1
/*******************************************************************************
2
* Copyright (c) 2007, 2011 Intel Corporation and others.
3
* All rights reserved. This program and the accompanying materials
4
* are made available under the terms of the Eclipse Public License v1.0
5
* which accompanies this distribution, and is available at
6
* http://www.eclipse.org/legal/epl-v10.html
9
* Intel Corporation - Initial API and implementation
10
* Anton Leherbauer (Wind River Systems)
11
* James Blackburn (Broadcom Corp.)
13
*******************************************************************************/
14
package org.eclipse.cdt.internal.core;
16
import java.lang.reflect.InvocationHandler;
17
import java.lang.reflect.Method;
18
import java.lang.reflect.Proxy;
19
import java.util.HashMap;
20
import java.util.LinkedHashSet;
23
import org.eclipse.cdt.core.AbstractCExtension;
24
import org.eclipse.cdt.core.CCorePlugin;
25
import org.eclipse.cdt.core.CDescriptorEvent;
26
import org.eclipse.cdt.core.ICDescriptor;
27
import org.eclipse.cdt.core.ICDescriptorManager;
28
import org.eclipse.cdt.core.ICExtension;
29
import org.eclipse.cdt.core.ICExtensionReference;
30
import org.eclipse.cdt.core.ICOwnerInfo;
31
import org.eclipse.cdt.core.settings.model.ICConfigExtensionReference;
32
import org.eclipse.cdt.core.settings.model.ICConfigurationDescription;
33
import org.eclipse.cdt.core.settings.model.ICProjectDescription;
34
import org.eclipse.cdt.core.settings.model.ICStorageElement;
35
import org.eclipse.cdt.core.settings.model.util.CDataUtil;
36
import org.eclipse.cdt.core.settings.model.util.CExtensionUtil;
37
import org.eclipse.cdt.internal.core.settings.model.CConfigurationDescriptionCache;
38
import org.eclipse.cdt.internal.core.settings.model.CConfigurationSpecSettings;
39
import org.eclipse.cdt.internal.core.settings.model.CProjectDescriptionManager;
40
import org.eclipse.cdt.internal.core.settings.model.ExceptionFactory;
41
import org.eclipse.cdt.internal.core.settings.model.IInternalCCfgInfo;
42
import org.eclipse.cdt.internal.core.settings.model.SynchronizedStorageElement;
43
import org.eclipse.cdt.internal.core.settings.model.xml.XmlStorage;
44
import org.eclipse.cdt.internal.core.settings.model.xml.XmlStorageElement;
45
import org.eclipse.core.resources.IProject;
46
import org.eclipse.core.resources.ResourcesPlugin;
47
import org.eclipse.core.runtime.CoreException;
48
import org.eclipse.core.runtime.IConfigurationElement;
49
import org.eclipse.core.runtime.IProgressMonitor;
50
import org.eclipse.core.runtime.IStatus;
51
import org.eclipse.core.runtime.NullProgressMonitor;
52
import org.eclipse.core.runtime.Status;
53
import org.eclipse.core.runtime.jobs.ILock;
54
import org.eclipse.core.runtime.jobs.Job;
55
import org.w3c.dom.Element;
56
import org.w3c.dom.Node;
58
import com.ibm.icu.text.MessageFormat;
61
* Concrete ICDescriptor for a Project.
63
* There is only one of these per project. Settings are serialized as storage elements
64
* as children of the root of the project description. Methods which change or access data
65
* on the descriptor use the Eclipse ILock 'fLock' on the given descriptor instance.
67
* Structural changes made to extension elements are persisted immediately to
68
* the project description.
70
* Changes made to child storage elements are serialized to the project description
71
* with saveProjectData(...) and the serializingJob.
73
* Users should consider using {@link ICDescriptorManager#runDescriptorOperation} for threadsafe
74
* access to the project's configuration. However failing this does provide some basic
75
* concurrency on {@link #getProjectStorageElement(String)} by wrapping the returned
76
* ICStorageElement in an {@link SynchronizedStorageElement}. Note that this is best
77
* effort, so concurrent structural changes to the tree (such as one thread removing
78
* an element from a tree while another is writing to it) may result in inconsistent data
82
final public class CConfigBasedDescriptor implements ICDescriptor {
83
private static final String CEXTENSION_NAME = "cextension"; //$NON-NLS-1$
84
/** The current default setting configuration description
85
* Equivalent to {@link ICProjectDescription#getDefaultSettingConfiguration()}*/
86
private ICConfigurationDescription fCfgDes;
87
private COwner fOwner;
89
/** Map: storageModule ID -> ICStorageElement <br/>
90
* CDescriptor's map of so far uncommited storage elements. */
91
private final Map<String, SynchronizedStorageElement> fStorageDataElMap = new HashMap<String, SynchronizedStorageElement>();
92
private volatile boolean fIsDirty;
93
/** Current CDescriptor Event which tracks changes between operationStart & operationStop */
94
private CDescriptorEvent fOpEvent;
95
/** Flag indicating whether an operation has started */
96
private volatile boolean fIsOpStarted;
98
/** This descriptor's lock */
99
final ILock fLock = Job.getJobManager().newLock();
101
* The Job the actually does the data applying (by getting and setting the current project description)
102
* saveProjectData never does the saving itself, rather it schedules this job to run.
103
* During the setCProjectDescriptionOperation the changes in this ICDescriptor are synchronized into the
104
* project description being persisted.
106
class SerializingJob extends Job {
107
public SerializingJob(String name) {
110
// This rule must contain that in SetCProjectDescriptionOperation
111
// (Resource scheduling rules are always obtained before data structure locks to prevent deadlocks.)
112
setRule(ResourcesPlugin.getWorkspace().getRoot());
115
protected IStatus run(IProgressMonitor monitor) {
118
// No point scheduling the run if the project is closed...
119
if (!getProject().isAccessible())
120
return Status.CANCEL_STATUS;
122
} catch (CoreException e) {
127
return Status.OK_STATUS;
130
public void serialize() throws CoreException {
131
if (!getProject().isAccessible())
132
throw ExceptionFactory.createCoreException(MessageFormat.format(CCorePlugin.getResourceString("ProjectDescription.ProjectNotAccessible"), new Object[] {getProject().getName()})); //$NON-NLS-1$
134
ICProjectDescription des = fCfgDes.getProjectDescription();
135
if(des.isCdtProjectCreating())
136
des.setCdtProjectCreated();
137
CProjectDescriptionManager.getInstance().setProjectDescription(getProject(), des);
142
SerializingJob serializingJob = new SerializingJob("CConfigBasedDescriptor Serializing Job"); //$NON-NLS-1$ (system)
145
* Concrete implementation of ICExtensionReference based on ICConfigExtensionReference elements.
146
* In the old world ICExtensions had no notion of which configuration they belong to.
147
* As a result all state that would have be persisted at the ICExtension level is saved to all
148
* the configurations in the project
150
* This is a lightweight proxy onto ICConfigExtensionReference and doesn't hold any state
151
* itself (though alters the isDirty state and descriptor event of the containing Descriptor).
153
final class CConfigBaseDescriptorExtensionReference implements ICExtensionReference {
154
/** The ICConfigExtensionReference this is based on -- the identifying feature of this ICExtensionReference */
155
private final ICConfigExtensionReference fCfgExtRef;
156
CConfigBaseDescriptorExtensionReference(ICConfigExtensionReference cfgRef){
160
public ICExtension createExtension() throws CoreException {
161
AbstractCExtension cExtension = null;
162
IConfigurationElement el = CExtensionUtil.getFirstConfigurationElement(fCfgExtRef, CEXTENSION_NAME, false);
163
cExtension = (AbstractCExtension)el.createExecutableExtension("run"); //$NON-NLS-1$
164
cExtension.setExtensionReference(fCfgExtRef);
165
cExtension.setProject(getProject());
169
public ICDescriptor getCDescriptor() {
170
return CConfigBasedDescriptor.this;
173
public String getExtension() {
174
return fCfgExtRef.getExtensionPoint();
177
public String getExtensionData(String key) {
178
return fCfgExtRef.getExtensionData(key);
181
public IConfigurationElement[] getExtensionElements()
182
throws CoreException {
183
IConfigurationElement el = CExtensionUtil.getFirstConfigurationElement(fCfgExtRef, CEXTENSION_NAME, false);
185
return el.getChildren();
186
return new IConfigurationElement[0];
189
public String getID() {
190
return fCfgExtRef.getID();
193
public void setExtensionData(String key, String value)
194
throws CoreException {
195
if(!CDataUtil.objectsEqual(fCfgExtRef.getExtensionData(key), value)){
197
fCfgExtRef.setExtensionData(key, value);
199
if(isOperationStarted())
200
setOpEvent(new CDescriptorEvent(CConfigBasedDescriptor.this, CDescriptorEvent.CDTPROJECT_CHANGED, 0));
204
public boolean equals(Object obj) {
207
if (obj instanceof CConfigBaseDescriptorExtensionReference)
208
return fCfgExtRef.equals(((CConfigBaseDescriptorExtensionReference)obj).fCfgExtRef);
209
return fCfgExtRef.equals(obj);
212
public int hashCode() {
213
return fCfgExtRef.hashCode();
217
public CConfigBasedDescriptor(ICConfigurationDescription des) throws CoreException{
221
public CConfigBasedDescriptor(ICConfigurationDescription des, boolean write) throws CoreException{
222
updateConfiguration(des, write);
226
* Persist the current project description (to persist changes to the ICExtensions)
228
* @throws CoreException
230
void apply(boolean force) throws CoreException {
235
// If we're already serializing the project description, schedule a job
236
// to perform the serialization...
237
if (CProjectDescriptionManager.getInstance().isCurrentThreadSetProjectDescription()) {
238
serializingJob.schedule();
242
// Deadlock warning: path entry, for example, can do getStorageElement
243
// in resource delta (while holding the workspace lock). As CModelOperation
244
// runs the job as a workspace runnable, this leads to potential deadlock.
246
// So before applying, we ensure that we hold the project resource rule
247
// before getting the 'lock' on the datastructures
249
// final IProject project = getProject();
251
final int lockDepth = fLock.getDepth();
252
for (int i = 0; i < lockDepth ; ++i)
256
// This rule must contain that in SetCProjectDescriptionOperation
257
Job.getJobManager().beginRule(ResourcesPlugin.getWorkspace().getRoot(), new NullProgressMonitor());
260
serializingJob.serialize();
262
if (lockDepth == 0) // Only release the lock if it wasn't previously held on entrance to this method
264
else // Reacquire the lock to the appropriate depth
265
for (int i = 0; i < lockDepth - 1; i++)
269
Job.getJobManager().endRule(ResourcesPlugin.getWorkspace().getRoot());
273
private void checkApply() throws CoreException {
281
void setDirty(boolean dirty){
287
* @see org.eclipse.cdt.core.ICDescriptor#create(java.lang.String, java.lang.String)
289
public ICExtensionReference create(String extensionPoint, String id) throws CoreException {
292
ICConfigExtensionReference ref = fCfgDes.create(extensionPoint, id);
294
//write is done for all configurations to avoid "data loss" on configuration change
295
ICProjectDescription des = fCfgDes.getProjectDescription();
296
ICConfigurationDescription cfgs[] = des.getConfigurations();
297
for (ICConfigurationDescription cfg : cfgs) {
300
cfg.create(extensionPoint, id);
301
} catch (CoreException e){
307
ICExtensionReference r = new CConfigBaseDescriptorExtensionReference(ref);
310
if(isOperationStarted())
311
setOpEvent(new CDescriptorEvent(this, CDescriptorEvent.CDTPROJECT_CHANGED, CDescriptorEvent.EXTENSION_CHANGED));
319
* Equivalent to {@code updateConfiguration(des, true)}
320
* @param des the new ICConfigurationDescription
321
* @throws CoreException
323
public void updateConfiguration(ICConfigurationDescription des) throws CoreException{
324
updateConfiguration(des, true);
328
* Update the currently default (settings) configuration
331
* @throws CoreException
333
public void updateConfiguration(ICConfigurationDescription des, boolean write) throws CoreException{
336
if(write && des instanceof CConfigurationDescriptionCache)
337
throw new IllegalArgumentException();
340
CConfigurationSpecSettings settings = ((IInternalCCfgInfo)fCfgDes).getSpecSettings();
341
fOwner = settings.getCOwner();
348
* Attempt to return an ICExtensionReference array based on the ICConfigExtensionReferences
349
* contained in this project description (which match the extensionPointId).
351
* Fetches all the ICConfigExtensionReferences from the project's configurations.
353
* Previously this cached the current set of ICExtensionReferences,
354
* but this cache was never used (it was always overwritten by this method).
356
* FIXME re-add caching (the current behaviour mirrors the previous behaviour -- just tidier)
357
* @return an array of ICExtenionReference
359
public ICExtensionReference[] get(String extensionPoint) {
362
LinkedHashSet<ICExtensionReference> extRefs = new LinkedHashSet<ICExtensionReference>();
364
// Add the ICConfigExtensionReferences for the current configuration description
365
for (ICConfigExtensionReference cfgRes : fCfgDes.get(extensionPoint))
366
extRefs.add(new CConfigBaseDescriptorExtensionReference(cfgRes));
368
for (ICConfigurationDescription cfg : fCfgDes.getProjectDescription().getConfigurations())
369
if (!cfg.equals(fCfgDes))
370
for (ICConfigExtensionReference cfgRes : fCfgDes.get(extensionPoint))
371
extRefs.add(new CConfigBaseDescriptorExtensionReference(cfgRes));
373
return extRefs.toArray(new ICExtensionReference[extRefs.size()]);
381
* @see org.eclipse.cdt.core.ICDescriptor#get(java.lang.String, boolean)
383
public ICExtensionReference[] get(String extensionPoint, boolean update) throws CoreException {
386
ICExtensionReference[] refs = get(extensionPoint);
387
if(refs.length == 0 && update){
388
fOwner.update(getProject(), this, extensionPoint);
390
refs = get(extensionPoint);
400
* @see org.eclipse.cdt.core.ICDescriptor#getPlatform()
402
public String getPlatform() {
405
return fOwner.getPlatform();
413
* @see org.eclipse.cdt.core.ICDescriptor#getProject()
415
public IProject getProject() {
418
return fCfgDes.getProjectDescription().getProject();
425
* Note that in the current implementation of the xml based project description
426
* it is not safe to work on the same storage element in more than one thread.
428
* It is likely that doing so will return a concurrent modification exception on the
429
* returned ICStorageElement. We must allow this as this is how the existing implementation
432
public ICStorageElement getProjectStorageElement(String id) throws CoreException {
435
// Check if the storage element already exists in our local map
436
SynchronizedStorageElement storageEl = fStorageDataElMap.get(id);
437
if(storageEl == null){
438
// Check in the Proejct Description
439
ICStorageElement el = fCfgDes.getProjectDescription().getStorage(id, false);
441
// Fall-back to checking in the configuration (which is how it used ot be)
443
el = fCfgDes.getStorage(id, true);
445
el = el.createCopy();
446
} catch (UnsupportedOperationException e) {
447
throw ExceptionFactory.createCoreException(e);
449
storageEl = SynchronizedStorageElement.synchronizedElement(el);
450
fStorageDataElMap.put(id, storageEl);
459
* Backwards compatibility method which provides an XML Element.
460
* Currently relies on the fact that the only implementation if ICStorageElement
461
* in the core is XmlStorageElement.
463
public Element getProjectData(String id) throws CoreException {
466
// Check if the storage element already exists in our local map
467
SynchronizedStorageElement storageEl = fStorageDataElMap.get(id);
469
if(storageEl == null) {
470
el = fCfgDes.getProjectDescription().getStorage(id, false);
472
el = fCfgDes.getStorage(id, true);
474
el = el.createCopy();
475
} catch (UnsupportedOperationException e) {
476
throw ExceptionFactory.createCoreException(e);
479
if (!(el instanceof XmlStorageElement))
480
throw ExceptionFactory.createCoreException(
481
"Internal Error: getProjectData(...) currently only supports XmlStorageElement types.", new Exception()); //$NON-NLS-1$
483
// Get the underlying Xml Element
484
final Element xmlEl = ((XmlStorageElement)el).fElement;
485
// This proxy synchronizes the storage element's root XML Element
486
el = new XmlStorageElement((Element)Proxy.newProxyInstance(Element.class.getClassLoader(), new Class[]{Element.class}, new InvocationHandler(){
487
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
488
Method realMethod = xmlEl.getClass().getMethod(method.getName(), method.getParameterTypes());
489
// Now just execute the method
490
synchronized (xmlEl) {
491
// If requesting the parent node, then we need another proxy
492
// so that parent.removeChildNode(...) 'does the right thing'
493
if (method.getName().equals("getParentNode")) { //$NON-NLS-1$
494
final Node parent = (Node)realMethod.invoke(xmlEl, args);
495
Node parentProxy = (Node)Proxy.newProxyInstance(Node.class.getClassLoader(), new Class[]{Node.class}, new InvocationHandler(){
496
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
497
Method realMethod = parent.getClass().getMethod(method.getName(), method.getParameterTypes());
498
synchronized (xmlEl) {
499
// Handle the remove child case
500
if (method.getName().equals("removeChild")) { //$NON-NLS-1$
501
if (args[0] instanceof Element && ((Element)args[0]).getAttribute(
502
XmlStorage.MODULE_ID_ATTRIBUTE).length() > 0) {
503
ICStorageElement removed = removeProjectStorageElement(((Element)args[0]).getAttribute(
504
XmlStorage.MODULE_ID_ATTRIBUTE));
506
return ((XmlStorageElement)((SynchronizedStorageElement)removed).getOriginalElement()).fElement;
510
// else return the realMethod
511
return realMethod.invoke(parent, args);
517
// Otherwise just execute the method
518
return realMethod.invoke(xmlEl, args);
523
storageEl = SynchronizedStorageElement.synchronizedElement(el, xmlEl);
524
fStorageDataElMap.put(id, storageEl);
526
el = storageEl.getOriginalElement();
527
if (!(el instanceof XmlStorageElement))
528
throw ExceptionFactory.createCoreException(
529
"Internal Error: getProjectData(...) currently only supports XmlStorageElement types.", new Exception()); //$NON-NLS-1$
532
return ((XmlStorageElement)el).fElement;
538
public ICStorageElement removeProjectStorageElement(String id) throws CoreException {
541
return fStorageDataElMap.put(id, null);
547
public ICOwnerInfo getProjectOwner() {
556
public void remove(ICExtensionReference extension) throws CoreException {
559
ICConfigExtensionReference ref = ((CConfigBaseDescriptorExtensionReference)extension).fCfgExtRef;
562
// write is done for all configurations to avoid "data loss" on configuration change
563
for (ICConfigurationDescription cfg : fCfgDes.getProjectDescription().getConfigurations()) {
566
ICConfigExtensionReference rs[] = cfg.get(ref.getExtensionPoint());
567
for (ICConfigExtensionReference element : rs) {
568
if(ref.getID().equals(element.getID())){
573
} catch (CoreException e) {
580
if(isOperationStarted())
581
setOpEvent(new CDescriptorEvent(this, CDescriptorEvent.CDTPROJECT_CHANGED, CDescriptorEvent.EXTENSION_CHANGED));
587
public void remove(String extensionPoint) throws CoreException {
590
fCfgDes.remove(extensionPoint);
591
//write is done for all configurations to avoid "data loss" on configuration change
592
for (ICConfigurationDescription cfg : fCfgDes.getProjectDescription().getConfigurations()) {
595
cfg.remove(extensionPoint);
596
} catch (CoreException e) {
603
if(isOperationStarted())
604
setOpEvent(new CDescriptorEvent(this, CDescriptorEvent.CDTPROJECT_CHANGED, CDescriptorEvent.EXTENSION_CHANGED));
612
* @see org.eclipse.cdt.core.ICDescriptor#saveProjectData()
614
public void saveProjectData() throws CoreException {
617
// Reconcile changes into the current project description
618
if(reconcile(this, fCfgDes.getProjectDescription())) {
622
if(isOperationStarted())
623
setOpEvent(new CDescriptorEvent(this, CDescriptorEvent.CDTPROJECT_CHANGED, 0));
632
* @see org.eclipse.cdt.core.ICDescriptor#getConfigurationDescription()
634
public ICConfigurationDescription getConfigurationDescription() {
644
* Event handling routines
647
void setOpEvent(CDescriptorEvent event) {
650
if(!isOperationStarted())
653
if (event.getType() == CDescriptorEvent.CDTPROJECT_ADDED) {
655
} else if (event.getType() == CDescriptorEvent.CDTPROJECT_REMOVED) {
658
if (fOpEvent == null) {
660
} else if ( (fOpEvent.getFlags() & event.getFlags()) != event.getFlags()) {
661
fOpEvent = new CDescriptorEvent(event.getDescriptor(), event.getType(),
662
fOpEvent.getFlags() | event.getFlags());
670
boolean isOperationStarted(){
674
void operationStart(){
679
* Mark the operation as over -- return the CDescriptorEvent
682
CDescriptorEvent operationStop(){
685
fIsOpStarted = false;
686
CDescriptorEvent e = fOpEvent;
696
* The reconcile methods below are for copying storage element changes from the current
697
* CConfigBasedDescriptor to the passed in writable project description
701
* Copies the changes made to the CConfigBasedDescriptor to the ICProjectDescription
703
* The changes are reconciled into all the project's configurations!
706
* @return boolean indicating whether changes were made
708
public static boolean reconcile(CConfigBasedDescriptor descriptor, ICProjectDescription des) throws CoreException {
710
descriptor.fLock.acquire();
712
Map<String, SynchronizedStorageElement> map = descriptor.fStorageDataElMap;
713
boolean reconciled = false;
715
for (Map.Entry<String, SynchronizedStorageElement> entry : map.entrySet()) {
716
String id = entry.getKey();
717
SynchronizedStorageElement synchStor = entry.getValue();
719
if (synchStor != null ) {
720
// Lock the synchronized storage element to prevent further changes
721
synchronized (synchStor.lock()) {
722
if(reconcile(id, synchStor.getOriginalElement(), des))
726
if (reconcile(id, null, des))
733
descriptor.fLock.release();
737
private static boolean reconcile(String id, ICStorageElement newStorEl, ICProjectDescription des) throws CoreException {
738
ICStorageElement storEl = des.getStorage(id, false);
740
boolean modified = false;
743
if(newStorEl == null){
744
des.removeStorage(id);
747
if(!newStorEl.equals(storEl)){
748
des.importStorage(id, newStorEl);
753
if(newStorEl != null){
754
des.importStorage(id, newStorEl);
759
// Now storing the descriptor info directly in the Project Description.
760
// Ensure that the setting is no longer stored in all the configurations
761
for (ICConfigurationDescription cfgDes : des.getConfigurations()) {
762
ICStorageElement el = cfgDes.getStorage(id, false);
764
cfgDes.removeStorage(id);