blob: f874daba4ad5d45a590f3af8e9b58fdd40008fa6 [file] [log] [blame]
/******************************************************************************
* Copyright (c) 2010 Oracle
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* Contributors:
* Konstantin Komissarchik - initial implementation and ongoing maintenance
******************************************************************************/
package org.eclipse.wst.common.project.facet.core.internal;
import static org.eclipse.wst.common.project.facet.core.util.internal.FileUtil.validateEdit;
import static org.eclipse.wst.common.project.facet.core.util.internal.ProgressMonitorUtil.beginTask;
import static org.eclipse.wst.common.project.facet.core.util.internal.ProgressMonitorUtil.done;
import static org.eclipse.wst.common.project.facet.core.util.internal.ProgressMonitorUtil.submon;
import static org.eclipse.wst.common.project.facet.core.util.internal.ProgressMonitorUtil.worked;
import static org.eclipse.wst.common.project.facet.core.util.internal.XmlUtil.escape;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IFolder;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.IWorkspaceRunnable;
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.MultiStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.wst.common.project.facet.core.ActionConfig;
import org.eclipse.wst.common.project.facet.core.FacetedProjectFrameworkException;
import org.eclipse.wst.common.project.facet.core.IActionDefinition;
import org.eclipse.wst.common.project.facet.core.IDelegate;
import org.eclipse.wst.common.project.facet.core.IFacetedProject;
import org.eclipse.wst.common.project.facet.core.IFacetedProjectValidator;
import org.eclipse.wst.common.project.facet.core.IFacetedProjectWorkingCopy;
import org.eclipse.wst.common.project.facet.core.IProjectFacet;
import org.eclipse.wst.common.project.facet.core.IProjectFacetVersion;
import org.eclipse.wst.common.project.facet.core.ProjectFacetsManager;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectEvent;
import org.eclipse.wst.common.project.facet.core.events.IFacetedProjectListener;
import org.eclipse.wst.common.project.facet.core.events.ITargetedRuntimesChangedEvent;
import org.eclipse.wst.common.project.facet.core.events.internal.FixedFacetsChangedEvent;
import org.eclipse.wst.common.project.facet.core.events.internal.LegacyListenerAdapter;
import org.eclipse.wst.common.project.facet.core.events.internal.PrimaryRuntimeChangedEvent;
import org.eclipse.wst.common.project.facet.core.events.internal.ProjectFacetActionEvent;
import org.eclipse.wst.common.project.facet.core.events.internal.ProjectListenerRegistry;
import org.eclipse.wst.common.project.facet.core.events.internal.ProjectModifiedEvent;
import org.eclipse.wst.common.project.facet.core.events.internal.TargetedRuntimesChangedEvent;
import org.eclipse.wst.common.project.facet.core.runtime.IRuntime;
import org.eclipse.wst.common.project.facet.core.runtime.RuntimeManager;
import org.eclipse.wst.common.project.facet.core.runtime.internal.UnknownRuntime;
import org.eclipse.wst.common.project.facet.core.util.internal.ObjectReference;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
/**
* @author <a href="mailto:konstantin.komissarchik@oracle.com">Konstantin Komissarchik</a>
*/
/*
* Synchronization Notes
*
* 1. There is an internal lock object that's used to synchronize access to
* the data structure. By synchronizing on an internal object, outside code
* cannot cause a deadlock by synchronizing on the FacetedProject object.
*
* 2. Readers synchronize on the lock object for the duration of the method
* call. This protects the readers from writers and makes sure that reader
* is not reading stale data from thread's local memory.
*
* 3. All collections that are returned by the reader methods are guaranteed
* to not change after the fact. This is implemented through a copy-on-write
* policy.
*
* 4. Writers synchronize on the lock object briefly at the start of the method
* and mark the FacetedProject as being modified. If the project is already
* being modified, the new writer will wait. Inside the bodies of the
* modifier methods, the writer thread is only synchronized on the lock
* object while modifying the internal datastructures. These synchronization
* sections are kept short and they never span over code that might modify
* file system resources. This is done to prevent deadlocks. Once the write
* is complete, the writer thread synchronizes on the lock object, resets the
* "being modified" flag, and notifies any writers that may be waiting.
*/
public final class FacetedProject
implements IFacetedProject
{
public static final String METADATA_FILE
= ".settings/" + FacetCorePlugin.PLUGIN_ID + ".xml"; //$NON-NLS-1$ //$NON-NLS-2$
private static final String TRACING_DELEGATE_CALLS
= FacetCorePlugin.PLUGIN_ID + "/delegate/calls"; //$NON-NLS-1$
private static final String TRACING_EVENTS
= FacetCorePlugin.PLUGIN_ID + "/events/project"; //$NON-NLS-1$
private static final String TRACING_EVENTS_STACKTRACE
= FacetCorePlugin.PLUGIN_ID + "/events/project/stacktrace"; //$NON-NLS-1$
private static final String EL_RUNTIME = "runtime"; //$NON-NLS-1$
private static final String EL_SECONDARY_RUNTIME = "secondary-runtime"; //$NON-NLS-1$
private static final String EL_FIXED = "fixed"; //$NON-NLS-1$
private static final String EL_INSTALLED = "installed"; //$NON-NLS-1$
private static final String ATTR_NAME = "name"; //$NON-NLS-1$
private static final String ATTR_FACET = "facet"; //$NON-NLS-1$
private static final String ATTR_VERSION = "version"; //$NON-NLS-1$
private final IProject project;
private final Set<IProjectFacetVersion> facets;
private final Set<IProjectFacetVersion> facetsReadOnly;
private final Set<IProjectFacet> fixed;
private final Set<IProjectFacet> fixedReadOnly;
private final Map<String,ProjectFacet> unknownFacets;
private final Map<IProjectFacet,String> activeFacetAliases;
private final Map<IProjectFacetVersion,String> activeFacetVersionAliases;
private final Set<String> targetedRuntimes;
private String primaryRuntime;
IFile f;
private long fModificationStamp = -1;
private final ProjectListenerRegistry listeners;
private final Object lock = new Object();
private boolean isBeingModified = false;
private Thread modifierThread = null;
private Exception parsingException;
private boolean isDeleted;
FacetedProject( final IProject project )
throws CoreException
{
this.project = project;
this.facets = new CopyOnWriteArraySet<IProjectFacetVersion>();
this.facetsReadOnly = Collections.unmodifiableSet( this.facets );
this.fixed = new CopyOnWriteArraySet<IProjectFacet>();
this.fixedReadOnly = Collections.unmodifiableSet( this.fixed );
this.unknownFacets = new HashMap<String,ProjectFacet>();
this.activeFacetAliases = new HashMap<IProjectFacet,String>();
this.activeFacetVersionAliases = new HashMap<IProjectFacetVersion,String>();
this.targetedRuntimes = new CopyOnWriteArraySet<String>();
this.listeners = new ProjectListenerRegistry();
this.parsingException = null;
this.isDeleted = false;
this.f = project.getFile( METADATA_FILE );
refresh( true );
}
public IProject getProject()
{
return this.project;
}
public Set<IProjectFacetVersion> getProjectFacets()
{
synchronized( this.lock )
{
return this.facetsReadOnly;
}
}
public boolean hasProjectFacet( final IProjectFacet f )
{
synchronized( this.lock )
{
for( IProjectFacetVersion fv : this.facets )
{
if( fv.getProjectFacet() == f )
{
return true;
}
}
return false;
}
}
public boolean hasProjectFacet( final IProjectFacetVersion fv )
{
synchronized( this.lock )
{
return this.facets.contains( fv );
}
}
public IProjectFacetVersion getProjectFacetVersion( final IProjectFacet f )
{
synchronized( this.lock )
{
for( IProjectFacetVersion fv : this.facets )
{
if( fv.getProjectFacet() == f )
{
return fv;
}
}
return null;
}
}
public IProjectFacetVersion getInstalledVersion( final IProjectFacet f )
{
return getProjectFacetVersion( f );
}
public void installProjectFacet( final IProjectFacetVersion fv,
final Object config,
final IProgressMonitor monitor )
throws CoreException
{
final Action action
= new Action( Action.Type.INSTALL, fv, config );
modify( Collections.singleton( action ), monitor );
}
public void uninstallProjectFacet( final IProjectFacetVersion fv,
final Object config,
final IProgressMonitor monitor )
throws CoreException
{
final Action action
= new Action( Action.Type.UNINSTALL, fv, config );
modify( Collections.singleton( action ), monitor );
}
public void modify( final Set<Action> actions,
final IProgressMonitor monitor )
throws CoreException
{
final ObjectReference<Boolean> result = new ObjectReference<Boolean>( true );
final IWorkspaceRunnable wr = new IWorkspaceRunnable()
{
public void run( final IProgressMonitor monitor )
throws CoreException
{
beginModification();
try
{
final IFacetedProjectWorkingCopy fpjwc = createWorkingCopy();
try
{
for( Action action : actions )
{
final Action.Type type = action.getType();
final IProjectFacetVersion fv = action.getProjectFacetVersion();
if( type == Action.Type.INSTALL )
{
fpjwc.addProjectFacet( fv );
}
else if( type == Action.Type.VERSION_CHANGE )
{
fpjwc.changeProjectFacetVersion( fv );
}
else if( type == Action.Type.UNINSTALL )
{
fpjwc.removeProjectFacet( fv );
}
else
{
throw new IllegalStateException();
}
final Object config = action.getConfig();
if( config != null )
{
fpjwc.setProjectFacetActionConfig( fv.getProjectFacet(), config );
}
}
result.set( mergeChangesInternal( fpjwc, monitor ) );
}
finally
{
fpjwc.dispose();
}
}
finally
{
endModification();
}
}
};
final IWorkspace ws = ResourcesPlugin.getWorkspace();
ws.run( wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, monitor );
if( result.get() )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
private boolean modifyInternal( final Set<Action> actions,
final IProgressMonitor monitor )
throws CoreException
{
beginTask( monitor, "", actions.size() * 100 ); //$NON-NLS-1$
try
{
validateNotDeleted();
final IStatus st = ProjectFacetsManager.check( this.facets, actions );
if( ! st.isOK() )
{
throw new CoreException( st );
}
// Perform batched validateEdit call.
final Set<IFile> files = new HashSet<IFile>();
files.add( this.f );
for( Action action : actions )
{
final Object actionConfig = action.getConfig();
if( actionConfig instanceof ActionConfig )
{
files.addAll( ( (ActionConfig) actionConfig ).getValidateEditFiles() );
}
}
if( ! validateEdit( files ) )
{
return false;
}
// Sort the actions into the order of execution.
final List<Action> copy = new ArrayList<Action>( actions );
ProjectFacetsManager.sort( this.facets, copy );
// Execute the actions.
for( Action action : copy )
{
final Action.Type type = action.getType();
final ProjectFacetVersion fv = (ProjectFacetVersion) action.getProjectFacetVersion();
final IActionDefinition def = fv.getActionDefinition( this.facets, type );
final Object config = action.getConfig();
if( monitor != null )
{
final String subTaskDescriptionTemplate;
if( type == Action.Type.INSTALL )
{
subTaskDescriptionTemplate = Resources.taskInstallingFacet;
}
else if( type == Action.Type.UNINSTALL )
{
subTaskDescriptionTemplate = Resources.taskUninstallingFacet;
}
else if( type == Action.Type.VERSION_CHANGE )
{
subTaskDescriptionTemplate = Resources.taskChangingFacetVersion;
}
else
{
throw new IllegalStateException();
}
final String subTaskDescription
= NLS.bind( subTaskDescriptionTemplate, fv.getProjectFacet().getLabel() );
monitor.subTask( subTaskDescription );
}
final IFacetedProjectEvent preEvent
= new ProjectFacetActionEvent( this, getPreEventType( type ), fv, config );
notifyListeners( preEvent );
worked( monitor, 10 );
final IDelegate delegate = ( (ActionDefinition) def ).getDelegate();
if( delegate == null )
{
worked( monitor, 80 );
}
else
{
callDelegate( fv, delegate, config, type, submon( monitor, 80 ) );
}
synchronized( this.lock )
{
apply( action );
}
save();
final IFacetedProjectEvent postEvent
= new ProjectFacetActionEvent( this, getPostEventType( type ), fv, config );
notifyListeners( postEvent );
worked( monitor, 10 );
}
return true;
}
finally
{
done( monitor );
}
}
public Set<IProjectFacet> getFixedProjectFacets()
{
synchronized( this.lock )
{
return this.fixedReadOnly;
}
}
public boolean isFixedProjectFacet( final IProjectFacet facet )
{
synchronized( this.lock )
{
return this.fixed.contains( facet );
}
}
public void setFixedProjectFacets( final Set<IProjectFacet> facets )
throws CoreException
{
final ObjectReference<Boolean> result = new ObjectReference<Boolean>( true );
final IWorkspaceRunnable wr = new IWorkspaceRunnable()
{
public void run( final IProgressMonitor monitor )
throws CoreException
{
beginModification();
try
{
result.set( setFixedProjectFacetsInternal( facets, monitor ) );
}
finally
{
endModification();
}
}
};
final IWorkspace ws = ResourcesPlugin.getWorkspace();
ws.run( wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, null );
if( result.get() )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
private boolean setFixedProjectFacetsInternal( final Set<IProjectFacet> facets,
final IProgressMonitor monitor )
throws CoreException
{
beginTask( monitor, "", 2 ); //$NON-NLS-1$
try
{
validateNotDeleted();
Set<IProjectFacet> oldFixedFacets = null;
synchronized( FacetedProject.this.lock )
{
if( equals( this.fixed, facets ) )
{
return false;
}
oldFixedFacets = new HashSet<IProjectFacet>( this.fixed );
this.fixed.clear();
this.fixed.addAll( facets );
}
save();
worked( monitor, 1 );
notifyListeners( new FixedFacetsChangedEvent( this, oldFixedFacets, facets ) );
worked( monitor, 1 );
return true;
}
finally
{
done( monitor );
}
}
/**
* @deprecated
*/
public IRuntime getRuntime()
{
return getPrimaryRuntime();
}
/**
* @deprecated
*/
public void setRuntime( final IRuntime runtime,
final IProgressMonitor monitor )
throws CoreException
{
final Set<IRuntime> runtimes
= runtime == null
? Collections.<IRuntime>emptySet() : Collections.singleton( runtime );
setTargetedRuntimes( runtimes, monitor );
}
public boolean isTargetable( final IRuntime runtime )
{
for( IProjectFacetVersion fv : getProjectFacets() )
{
if( ! runtime.supports( fv ) )
{
return false;
}
}
return true;
}
public boolean isTargeted( final IRuntime runtime )
{
for( IRuntime r : getTargetedRuntimes() )
{
if( r.getName().equals( runtime.getName() ) )
{
return true;
}
}
return false;
}
public Set<IRuntime> getTargetedRuntimes()
{
synchronized( this.lock )
{
final Set<IRuntime> result = new HashSet<IRuntime>();
for( String rname : this.targetedRuntimes )
{
result.add( getRuntimeFromName( rname ) );
}
return Collections.unmodifiableSet( result );
}
}
public void setTargetedRuntimes( final Set<IRuntime> runtimes,
final IProgressMonitor monitor )
throws CoreException
{
final ObjectReference<Boolean> result = new ObjectReference<Boolean>( true );
final IWorkspaceRunnable wr = new IWorkspaceRunnable()
{
public void run( final IProgressMonitor monitor )
throws CoreException
{
beginModification();
try
{
result.set( setTargetedRuntimesInternal( runtimes, monitor ) );
}
finally
{
endModification();
}
}
};
final IWorkspace ws = ResourcesPlugin.getWorkspace();
ws.run( wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, null );
if( result.get() )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
private boolean setTargetedRuntimesInternal( final Set<IRuntime> runtimes,
final IProgressMonitor monitor )
throws CoreException
{
beginTask( monitor, "", 2 ); //$NON-NLS-1$
try
{
validateNotDeleted();
final Set<IRuntime> oldRuntimes;
final Set<IRuntime> newRuntimes;
final IRuntime oldPrimary;
final IRuntime newPrimary;
synchronized( this.lock )
{
if( this.targetedRuntimes.size() == runtimes.size() )
{
boolean different = false;
for( IRuntime r : runtimes )
{
if( ! this.targetedRuntimes.contains( r.getName() ) )
{
different = true;
break;
}
}
if( ! different )
{
return false;
}
}
}
if( ! validateEdit( Collections.singleton( this.f ) ) )
{
return false;
}
synchronized( this.lock )
{
for( IRuntime runtime : runtimes )
{
for( IProjectFacetVersion fv : this.facets )
{
if( ! runtime.supports( fv ) )
{
final String msg
= NLS.bind( Resources.facetNotSupported, runtime.getLocalizedName(),
fv.toString() );
throw new CoreException( FacetCorePlugin.createErrorStatus( msg ) );
}
}
}
oldRuntimes = new HashSet<IRuntime>( getTargetedRuntimes() );
this.targetedRuntimes.clear();
for( IRuntime runtime : runtimes )
{
this.targetedRuntimes.add( runtime.getName() );
}
newRuntimes = new HashSet<IRuntime>( getTargetedRuntimes() );
oldPrimary = getPrimaryRuntime();
assignPrimaryRuntimeIfNecessary();
newPrimary = getPrimaryRuntime();
}
save();
worked( monitor, 1 );
final ITargetedRuntimesChangedEvent targetedRuntimesChangedEvent
= new TargetedRuntimesChangedEvent( this, oldRuntimes, newRuntimes );
notifyListeners( targetedRuntimesChangedEvent );
if( ! equals( oldPrimary, newPrimary ) )
{
notifyListeners( new PrimaryRuntimeChangedEvent( this, oldPrimary, newPrimary ) );
}
worked( monitor, 1 );
return true;
}
finally
{
done( monitor );
}
}
public void addTargetedRuntime( final IRuntime runtime,
final IProgressMonitor monitor )
throws CoreException
{
final Set<IRuntime> runtimes = new HashSet<IRuntime>( getTargetedRuntimes() );
runtimes.add( runtime );
setTargetedRuntimes( runtimes, monitor );
}
public void removeTargetedRuntime( final IRuntime runtime,
final IProgressMonitor monitor )
throws CoreException
{
final Set<IRuntime> runtimes = new HashSet<IRuntime>( getTargetedRuntimes() );
runtimes.remove( runtime );
setTargetedRuntimes( runtimes, monitor );
}
public IRuntime getPrimaryRuntime()
{
synchronized( this.lock )
{
if( this.primaryRuntime == null )
{
return null;
}
else
{
return getRuntimeFromName( this.primaryRuntime );
}
}
}
public void setPrimaryRuntime( final IRuntime runtime,
final IProgressMonitor monitor )
throws CoreException
{
final ObjectReference<Boolean> result = new ObjectReference<Boolean>( true );
final IWorkspaceRunnable wr = new IWorkspaceRunnable()
{
public void run( final IProgressMonitor monitor )
throws CoreException
{
beginModification();
try
{
result.set( setPrimaryRuntimeInternal( runtime, monitor ) );
}
finally
{
endModification();
}
}
};
final IWorkspace ws = ResourcesPlugin.getWorkspace();
ws.run( wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, null );
if( result.get() )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
private boolean setPrimaryRuntimeInternal( final IRuntime runtime,
final IProgressMonitor monitor )
throws CoreException
{
beginTask( monitor, "", 2 ); //$NON-NLS-1$
try
{
validateNotDeleted();
if( runtime == null )
{
throw new NullPointerException();
}
if( equals( this.primaryRuntime, runtime.getName() ) )
{
return false;
}
if( ! this.targetedRuntimes.contains( runtime.getName() ) )
{
final String msg = Resources.newPrimaryNotTargetRuntime;
final IStatus st = FacetCorePlugin.createErrorStatus( msg );
throw new CoreException( st );
}
final IRuntime oldPrimary;
synchronized( this.lock )
{
oldPrimary = getPrimaryRuntime();
this.primaryRuntime = runtime.getName();
}
save();
worked( monitor, 1 );
notifyListeners( new PrimaryRuntimeChangedEvent( this, oldPrimary, runtime ) );
worked( monitor, 1 );
return true;
}
finally
{
done( monitor );
}
}
private static IRuntime getRuntimeFromName( final String name )
{
IRuntime runtime = null;
if( RuntimeManager.isRuntimeDefined( name ) )
{
runtime = RuntimeManager.getRuntime( name );
}
if( runtime == null )
{
for( IRuntime r : RuntimeManager.getRuntimes() )
{
if( r.getAlternateNames().contains( name ) )
{
runtime = r;
break;
}
}
}
if( runtime == null )
{
runtime = new UnknownRuntime( name );
}
return runtime;
}
private void assignPrimaryRuntimeIfNecessary()
{
if( this.targetedRuntimes.isEmpty() )
{
this.primaryRuntime = null;
}
else
{
if( this.primaryRuntime == null ||
! this.targetedRuntimes.contains( this.primaryRuntime ) )
{
this.primaryRuntime = this.targetedRuntimes.iterator().next();
}
}
}
public IStatus validate()
{
return validate( null );
}
public IStatus validate( final IProgressMonitor monitor )
{
synchronized( this.lock )
{
beginTask( monitor, Resources.taskValidatingFacetedProject, 5 );
try
{
final List<String> errors = new ArrayList<String>();
final List<String> warnings = new ArrayList<String>();
// Check for parsing problems.
if( this.parsingException != null )
{
if( this.parsingException instanceof SAXException )
{
final String msg
= NLS.bind( Resources.metadataFileCorrupted,
this.f.getFullPath().toString() );
errors.add( msg );
}
else
{
final String msg
= NLS.bind( Resources.couldNotReadMetadataFile,
this.f.getFullPath().toString() );
errors.add( msg );
}
}
worked( monitor, 1 );
// Are any of the target runtimes not defined?
for( IRuntime r : getTargetedRuntimes() )
{
if( r instanceof UnknownRuntime )
{
final String msg = NLS.bind( Resources.runtimeNotDefined, r.getName() );
errors.add( msg );
}
}
worked( monitor, 1 );
// Is an installed facet not supported by the runtime?
for( IRuntime r : getTargetedRuntimes() )
{
for( IProjectFacetVersion fv : getProjectFacets() )
{
if( ! r.supports( fv ) )
{
final String msg
= NLS.bind( Resources.facetNotSupportedByTarget, fv.toString(),
r.getLocalizedName() );
errors.add( msg );
}
}
}
worked( monitor, 1 );
// Does the project contain any unknown facets or versions?
for( IProjectFacetVersion fv : getProjectFacets() )
{
final IProjectFacet f = fv.getProjectFacet();
if( f.getPluginId() == null )
{
final String msg
= NLS.bind( Resources.installedFacetNotFound, f.getId() );
warnings.add( msg );
}
else if( fv.getPluginId() == null )
{
final String msg
= NLS.bind( Resources.installedFacetVersionNotFound,
f.getId(), fv.getVersionString() );
warnings.add( msg );
}
}
worked( monitor, 1 );
// Compile the result.
if( errors.isEmpty() && warnings.isEmpty() )
{
return Status.OK_STATUS;
}
else
{
final String msg
= NLS.bind( Resources.projectValidationFailed,
this.project.getName() );
final IStatus[] starray
= new IStatus[ errors.size() + warnings.size() ];
for( int i = 0, n = errors.size(); i < n; i++ )
{
starray[ i ]
= new Status( IStatus.ERROR, FacetCorePlugin.PLUGIN_ID,
errors.get( i ) );
}
for( int i = 0, n = warnings.size(), offset = errors.size();
i < n; i++ )
{
starray[ i + offset ]
= new Status( IStatus.WARNING, FacetCorePlugin.PLUGIN_ID,
warnings.get( i ) );
}
return new MultiStatus( FacetCorePlugin.PLUGIN_ID, -1,
starray, msg, null );
}
}
finally
{
done( monitor );
}
}
}
public IFacetedProjectWorkingCopy createWorkingCopy()
{
synchronized( this.lock )
{
return new FacetedProjectWorkingCopy( this );
}
}
public void mergeChanges( final IFacetedProjectWorkingCopy fpjwc,
final IProgressMonitor monitor )
throws CoreException
{
final ObjectReference<Boolean> result = new ObjectReference<Boolean>( true );
final IWorkspaceRunnable wr = new IWorkspaceRunnable()
{
public void run( final IProgressMonitor monitor )
throws CoreException
{
beginModification();
try
{
result.set( mergeChangesInternal( fpjwc, monitor ) );
}
finally
{
endModification();
}
}
};
final IWorkspace ws = ResourcesPlugin.getWorkspace();
ws.run( wr, ws.getRoot(), IWorkspace.AVOID_UPDATE, monitor );
if( result.get() )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
private boolean mergeChangesInternal( final IFacetedProjectWorkingCopy fpjwc,
final IProgressMonitor monitor )
throws CoreException
{
final SubMonitor pm = SubMonitor.convert( monitor, 26 );
try
{
boolean modified = false;
// Figure out whether we can set runtimes before applying facet actions. This is better
// for performance reasons, but may not work if the project contains facets that are
// not supported by the new runtime. You can get into this situation if the user tries
// to simultaneously uninstall a facet and select a different runtime. The fallback
// solution for this situation is to set the targeted runtimes to an empty list first,
// then apply the facet actions, and then set the targeted runtimes to the new list.
// This is more drastic than necessary in all situations, but it is not clear that
// implementing additional optimizations is necessary either.
boolean canSetRuntimesFirst = true;
for( IProjectFacetVersion fv : getProjectFacets() )
{
for( IRuntime r : fpjwc.getTargetedRuntimes() )
{
if( ! r.supports( fv ) )
{
canSetRuntimesFirst = false;
break;
}
}
if( ! canSetRuntimesFirst )
{
break;
}
}
pm.subTask( Resources.taskConfiguringRuntimes );
if( canSetRuntimesFirst )
{
if( setTargetedRuntimesInternal( fpjwc.getTargetedRuntimes(), submon( pm, 2 ) ) )
{
modified = true;
}
if( getPrimaryRuntime() != null )
{
setPrimaryRuntimeInternal( fpjwc.getPrimaryRuntime(), submon( pm, 2 ) );
}
else
{
pm.worked( 2 );
}
}
else
{
final Set<IRuntime> emptySet = Collections.emptySet();
setTargetedRuntimesInternal( emptySet, submon( pm, 4 ) );
}
if( modifyInternal( fpjwc.getProjectFacetActions(),
pm.newChild( 16, SubMonitor.SUPPRESS_SETTASKNAME ) ) )
{
modified = true;
}
if( ! canSetRuntimesFirst )
{
pm.subTask( Resources.taskConfiguringRuntimes );
if( setTargetedRuntimesInternal( fpjwc.getTargetedRuntimes(), submon( pm, 2 ) ) )
{
modified = true;
}
if( getPrimaryRuntime() != null )
{
if( setPrimaryRuntimeInternal( fpjwc.getPrimaryRuntime(), submon( pm, 2 ) ) )
{
modified = true;
}
}
else
{
pm.worked( 2 );
}
}
if( setFixedProjectFacetsInternal( fpjwc.getFixedProjectFacets(), submon( pm, 2 ) ) )
{
modified = true;
}
return modified;
}
finally
{
pm.done();
}
}
public IMarker createErrorMarker( final String message )
throws CoreException
{
return createErrorMarker( IFacetedProjectValidator.BASE_MARKER_ID, message );
}
public IMarker createErrorMarker( final String type,
final String message )
throws CoreException
{
return createMarker( IMarker.SEVERITY_ERROR, type, message );
}
public IMarker createWarningMarker( final String message )
throws CoreException
{
return createWarningMarker( IFacetedProjectValidator.BASE_MARKER_ID, message );
}
public IMarker createWarningMarker( final String type,
final String message )
throws CoreException
{
return createMarker( IMarker.SEVERITY_WARNING, type, message );
}
private IMarker createMarker( final int severity,
final String type,
final String message )
throws CoreException
{
final IMarker[] existing
= this.project.findMarkers( type, false, IResource.DEPTH_ZERO );
for( int i = 0; i < existing.length; i++ )
{
final IMarker m = existing[ i ];
if( m.getAttribute( IMarker.SEVERITY, -1 ) == severity &&
m.getAttribute( IMarker.MESSAGE, "" ).equals( message ) ) //$NON-NLS-1$
{
return m;
}
}
final IMarker m = this.project.createMarker( type );
m.setAttribute( IMarker.MESSAGE, message );
m.setAttribute( IMarker.SEVERITY, severity );
return m;
}
public void addListener( final IFacetedProjectListener listener,
final IFacetedProjectEvent.Type... types )
{
this.listeners.addListener( listener, types );
}
public void removeListener( final IFacetedProjectListener listener )
{
this.listeners.removeListener( listener );
}
private void notifyListeners( final IFacetedProjectEvent event )
{
if( FacetCorePlugin.checkDebugOption( TRACING_EVENTS ) )
{
if( FacetCorePlugin.checkDebugOption( TRACING_EVENTS_STACKTRACE ) )
{
final StackTraceElement[] stacktrace = Thread.currentThread().getStackTrace();
System.out.println( "Faceted project event generated by:" ); //$NON-NLS-1$
for( int i = 2; i < stacktrace.length; i++ )
{
final StackTraceElement stackTraceElement = stacktrace[ i ];
final String c = stackTraceElement.getClassName();
final String m = stackTraceElement.getMethodName();
final String f = stackTraceElement.getFileName();
final int line = stackTraceElement.getLineNumber();
if( i != 2 )
{
System.out.print( " at " ); //$NON-NLS-1$
}
System.out.println( c + "." + m + "(" + f + ":" + line + ")" ); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
}
}
System.out.println( event );
}
this.listeners.notifyListeners( event );
FacetedProjectFrameworkImpl.getInstance().getProjectListenerRegistry().notifyListeners( event );
}
/**
* @deprecated
*/
public void addListener( final org.eclipse.wst.common.project.facet.core.IFacetedProjectListener listener )
{
this.listeners.addListener( new LegacyListenerAdapter( listener ),
IFacetedProjectEvent.Type.PROJECT_MODIFIED );
}
/**
* @deprecated
*/
public void removeListener( final org.eclipse.wst.common.project.facet.core.IFacetedProjectListener listener )
{
for( IFacetedProjectListener x
: this.listeners.getListeners( IFacetedProjectEvent.Type.PROJECT_MODIFIED ) )
{
if( x instanceof LegacyListenerAdapter &&
( (LegacyListenerAdapter) x ).getLegacyListener() == listener )
{
removeListener( x );
}
}
}
public Preferences getPreferences( final IProjectFacet facet )
throws BackingStoreException
{
return FacetedProjectFrameworkImpl.getInstance().getPreferences( facet, this );
}
private void beginModification()
throws CoreException
{
synchronized( this.lock )
{
while( this.isBeingModified )
{
if( this.modifierThread == Thread.currentThread() )
{
final String msg = Resources.illegalModificationMsg;
final IStatus st = FacetCorePlugin.createErrorStatus( msg );
throw new CoreException( st );
}
try
{
this.lock.wait();
}
catch( InterruptedException e ) {}
}
this.isBeingModified = true;
this.modifierThread = Thread.currentThread();
}
}
private void endModification()
{
synchronized( this.lock )
{
this.isBeingModified = false;
this.modifierThread = null;
this.lock.notifyAll();
}
}
private static IFacetedProjectEvent.Type getPreEventType( final Action.Type t )
{
if( t == Action.Type.INSTALL )
{
return IFacetedProjectEvent.Type.PRE_INSTALL;
}
else if( t == Action.Type.UNINSTALL )
{
return IFacetedProjectEvent.Type.PRE_UNINSTALL;
}
else if( t == Action.Type.VERSION_CHANGE )
{
return IFacetedProjectEvent.Type.PRE_VERSION_CHANGE;
}
else
{
throw new IllegalStateException();
}
}
private static IFacetedProjectEvent.Type getPostEventType( final Action.Type t )
{
if( t == Action.Type.INSTALL )
{
return IFacetedProjectEvent.Type.POST_INSTALL;
}
else if( t == Action.Type.UNINSTALL )
{
return IFacetedProjectEvent.Type.POST_UNINSTALL;
}
else if( t == Action.Type.VERSION_CHANGE )
{
return IFacetedProjectEvent.Type.POST_VERSION_CHANGE;
}
else
{
throw new IllegalStateException();
}
}
private void callDelegate( final IProjectFacetVersion fv,
final IDelegate delegate,
final Object config,
final Object context,
final IProgressMonitor monitor )
throws CoreException
{
final String tracingDelegateCallsStr
= Platform.getDebugOption( TRACING_DELEGATE_CALLS );
final boolean tracingDelegateCalls
= tracingDelegateCallsStr == null ? false
: tracingDelegateCallsStr.equals( "true" ); //$NON-NLS-1$
long timeStarted = -1;
if( tracingDelegateCalls )
{
final String msg
= Resources.bind( Resources.tracingDelegateStarting,
fv.getProjectFacet().getId(),
fv.getVersionString(), context.toString(),
delegate.getClass().getName() );
System.out.println( msg );
timeStarted = System.currentTimeMillis();
}
try
{
delegate.execute( this.project, fv, config, monitor );
}
catch( Exception e )
{
final String msg;
if( context == Action.Type.INSTALL )
{
msg = NLS.bind( Resources.failedOnInstall, fv );
}
else if( context == Action.Type.UNINSTALL )
{
msg = NLS.bind( Resources.failedOnUninstall, fv );
}
else if( context == Action.Type.VERSION_CHANGE )
{
msg = NLS.bind( Resources.failedOnVersionChange,
fv.getProjectFacet().getLabel(),
fv.getVersionString() );
}
else
{
throw new IllegalStateException( context.toString() );
}
final IStatus status
= new Status( IStatus.ERROR, FacetCorePlugin.PLUGIN_ID, 0,
msg, e );
final FacetedProjectFrameworkException wrapper
= new FacetedProjectFrameworkException( status );
if( e instanceof FacetedProjectFrameworkException &&
( (FacetedProjectFrameworkException) e ).isExpected() )
{
wrapper.setExpected( true );
}
throw wrapper;
}
if( context == Action.Type.UNINSTALL )
{
final IProjectFacet facet = fv.getProjectFacet();
try
{
getPreferences( facet ).removeNode();
}
catch( BackingStoreException e )
{
FacetCorePlugin.log( e );
}
}
if( tracingDelegateCalls )
{
final long duration = System.currentTimeMillis() - timeStarted;
final String msg
= NLS.bind( Resources.tracingDelegateFinished,
String.valueOf( duration ) );
System.out.println( msg );
}
}
private void apply( final Action action )
{
final Action.Type type = action.getType();
final IProjectFacetVersion fv = action.getProjectFacetVersion();
if( type == Action.Type.INSTALL )
{
this.facets.add( fv );
}
else if( type == Action.Type.UNINSTALL )
{
this.facets.remove( fv );
}
else if( type == Action.Type.VERSION_CHANGE )
{
for( IProjectFacetVersion x : this.facets )
{
if( x.getProjectFacet() == fv.getProjectFacet() )
{
this.facets.remove( x );
break;
}
}
this.facets.add( fv );
}
}
private void save()
throws CoreException
{
final StringWriter w = new StringWriter();
final PrintWriter out = new PrintWriter( w );
final String nl = System.getProperty( "line.separator" ); //$NON-NLS-1$
out.print( "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" ); //$NON-NLS-1$
out.print( nl );
out.print( "<faceted-project>" ); //$NON-NLS-1$
out.print( nl );
if( this.primaryRuntime != null )
{
out.print( " <runtime name=\"" ); //$NON-NLS-1$
out.print( escape( this.primaryRuntime ) );
out.print( "\"/>" ); //$NON-NLS-1$
out.print( nl );
}
for( String name : this.targetedRuntimes )
{
if( ! name.equals( this.primaryRuntime ) )
{
out.print( " <secondary-runtime name=\"" ); //$NON-NLS-1$
out.print( escape( name ) );
out.print( "\"/>" ); //$NON-NLS-1$
out.print( nl );
}
}
for( IProjectFacet f : this.fixed )
{
String fid = this.activeFacetAliases.get( f );
fid = ( fid == null ? f.getId() : fid );
out.print( " <fixed facet=\"" ); //$NON-NLS-1$
out.print( escape( fid ) );
out.print( "\"/>" ); //$NON-NLS-1$
out.print( nl );
}
for( IProjectFacetVersion fv : this.facets )
{
final IProjectFacet f = fv.getProjectFacet();
String fid = this.activeFacetAliases.get( f );
fid = ( fid == null ? f.getId() : fid );
String fverstr = this.activeFacetVersionAliases.get( fv );
fverstr = ( fverstr == null ? fv.getVersionString() : fverstr );
out.print( " <installed facet=\"" ); //$NON-NLS-1$
out.print( escape( fid ) );
out.print( "\" version=\"" ); //$NON-NLS-1$
out.print( escape( fverstr ) );
out.print( "\"/>" ); //$NON-NLS-1$
out.print( nl );
}
out.print( "</faceted-project>" ); //$NON-NLS-1$
out.print( nl );
final byte[] bytes;
try
{
bytes = w.getBuffer().toString().getBytes( "UTF-8" ); //$NON-NLS-1$
}
catch( UnsupportedEncodingException e )
{
// Unexpected. All JVMs are supposed to support UTF-8.
throw new RuntimeException( e );
}
final InputStream in = new ByteArrayInputStream( bytes );
if( this.f.exists() )
{
validateEdit( this.f );
this.f.setContents( in, true, false, null );
}
else
{
final IFolder parent = (IFolder) this.f.getParent();
if( ! parent.exists() )
{
parent.create( true, true, null );
}
this.f.create( in, true, null );
}
this.fModificationStamp = this.f.getModificationStamp();
this.parsingException = null;
}
public void refresh()
throws CoreException
{
refresh( false );
}
private void refresh( final boolean isInitializing )
throws CoreException
{
synchronized( this.lock )
{
if( this.isBeingModified )
{
return;
}
if( this.f.getModificationStamp() == this.fModificationStamp )
{
return;
}
beginModification();
try
{
this.facets.clear();
this.fixed.clear();
this.unknownFacets.clear();
this.activeFacetAliases.clear();
this.activeFacetVersionAliases.clear();
this.targetedRuntimes.clear();
this.primaryRuntime = null;
if( ! this.f.exists() )
{
this.fModificationStamp = -1;
}
else
{
this.fModificationStamp = this.f.getModificationStamp();
Element root = null;
try
{
root = parse( this.f );
this.parsingException = null;
}
catch( Exception e )
{
this.parsingException = e;
}
if( this.parsingException == null )
{
final Element[] elements = children( root );
for( int i = 0; i < elements.length; i++ )
{
final Element e = elements[ i ];
final String name = e.getNodeName();
if( name.equals( EL_RUNTIME ) )
{
this.primaryRuntime = e.getAttribute( ATTR_NAME );
this.targetedRuntimes.add( this.primaryRuntime );
}
else if( name.equals( EL_SECONDARY_RUNTIME ) )
{
this.targetedRuntimes.add( e.getAttribute( ATTR_NAME ) );
}
else if( name.equals( EL_FIXED ) )
{
final String id = e.getAttribute( ATTR_FACET );
final IProjectFacet f;
if( ProjectFacetsManager.isProjectFacetDefined( id ) )
{
f = ProjectFacetsManager.getProjectFacet( id );
if( ! f.getId().equals( id ) )
{
this.activeFacetAliases.put( f, id );
}
}
else
{
f = createUnknownFacet( id );
}
this.fixed.add( f );
}
else if( name.equals( EL_INSTALLED ) )
{
final String id = e.getAttribute( ATTR_FACET );
final String version = e.getAttribute( ATTR_VERSION );
final IProjectFacet f;
if( ProjectFacetsManager.isProjectFacetDefined( id ) )
{
f = ProjectFacetsManager.getProjectFacet( id );
if( ! f.getId().equals( id ) )
{
this.activeFacetAliases.put( f, id );
}
}
else
{
f = createUnknownFacet( id );
}
final IProjectFacetVersion fv;
if( f.hasVersion( version ) )
{
fv = f.getVersion( version );
if( ! fv.getVersionString().equals( version ) )
{
this.activeFacetVersionAliases.put( fv, version );
}
}
else
{
fv = createUnknownFacetVersion( f, version );
}
this.facets.add( fv );
}
}
}
}
}
finally
{
endModification();
}
}
// If we got here, the project was changed. All of the no-op checks return early. The
// exception is the case where the faceted project is being initialized for the first
// time in the workbench session (for instance after workbench restart or when an
// existing faceted project is imported into the workspace).
if( ! isInitializing )
{
notifyListeners( new ProjectModifiedEvent( this ) );
}
}
void markDeleted()
throws CoreException
{
synchronized( this.lock )
{
beginModification();
try
{
this.facets.clear();
this.fixed.clear();
this.unknownFacets.clear();
this.targetedRuntimes.clear();
this.primaryRuntime = null;
}
finally
{
endModification();
}
this.isDeleted = true;
}
}
private void validateNotDeleted()
throws CoreException
{
if( this.isDeleted )
{
final String msg = Resources.cannotModifyDeletedProject;
throw new CoreException( FacetCorePlugin.createErrorStatus( msg ) );
}
}
private ProjectFacet createUnknownFacet( final String id )
{
ProjectFacet f = this.unknownFacets.get( id );
if( f == null )
{
f = new ProjectFacet();
f.setId( id );
f.setLabel( id );
f.setDescription( "" ); //$NON-NLS-1$
this.unknownFacets.put( id, f );
}
return f;
}
private ProjectFacetVersion createUnknownFacetVersion( final IProjectFacet f,
final String version )
{
final ProjectFacetVersion fv;
if( f.hasVersion( version ) )
{
fv = (ProjectFacetVersion) f.getVersion( version );
}
else
{
fv = new ProjectFacetVersion();
fv.setProjectFacet( (ProjectFacet) f );
fv.setVersionString( version );
}
return fv;
}
private static Element parse( final IFile f )
throws CoreException, IOException, SAXException
{
final InputStream stream = f.getContents();
try
{
return parse( stream );
}
finally
{
try
{
stream.close();
}
catch( Exception e ) {}
}
}
private static Element parse( final InputStream stream )
throws IOException, SAXException
{
final DocumentBuilder docbuilder;
try
{
final DocumentBuilderFactory factory
= DocumentBuilderFactory.newInstance();
factory.setValidating( false );
docbuilder = factory.newDocumentBuilder();
docbuilder.setEntityResolver
(
new EntityResolver()
{
public InputSource resolveEntity( final String publicID,
final String systemID )
{
return new InputSource( new StringReader( "" ) ); //$NON-NLS-1$
}
}
);
}
catch( ParserConfigurationException e )
{
throw new RuntimeException( e );
}
return docbuilder.parse( stream ).getDocumentElement();
}
private Element[] children( final Element element )
{
final List<Element> list = new ArrayList<Element>();
final NodeList nl = element.getChildNodes();
for( int i = 0, n = nl.getLength(); i < n; i++ )
{
final Node node = nl.item( i );
if( node.getNodeType() == Node.ELEMENT_NODE )
{
list.add( (Element) node );
}
}
return list.toArray( new Element[ list.size() ] );
}
private static boolean equals( final Object obj1,
final Object obj2 )
{
if( obj1 == obj2 )
{
return true;
}
else if( obj1 == null || obj2 == null )
{
return false;
}
else
{
return obj1.equals( obj2 );
}
}
private static final class Resources
extends NLS
{
public static String failedOnInstall;
public static String failedOnUninstall;
public static String failedOnVersionChange;
public static String facetNotSupported;
public static String illegalModificationMsg;
public static String tracingDelegateStarting;
public static String tracingDelegateFinished;
public static String newPrimaryNotTargetRuntime;
public static String cannotModifyDeletedProject;
// Task Descriptions
public static String taskValidatingFacetedProject;
public static String taskInstallingFacet;
public static String taskUninstallingFacet;
public static String taskChangingFacetVersion;
public static String taskConfiguringRuntimes;
// Validation Messages
public static String projectValidationFailed;
public static String metadataFileCorrupted;
public static String couldNotReadMetadataFile;
public static String runtimeNotDefined;
public static String facetNotSupportedByTarget;
public static String installedFacetNotFound;
public static String installedFacetVersionNotFound;
static
{
initializeMessages( FacetedProject.class.getName(),
Resources.class );
}
public static final String bind( final String msg,
final String arg1,
final String arg2,
final String arg3,
final String arg4 )
{
return NLS.bind( msg, new Object[] { arg1, arg2, arg3, arg4 } );
}
}
}