1 /*******************************************************************************
2  * Copyright (c) 2004, 2015 IBM Corporation and others.
3  *
4  * This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License 2.0
6  * which accompanies this distribution, and is available at
7  * https://www.eclipse.org/legal/epl-2.0/
8  *
9  * SPDX-License-Identifier: EPL-2.0
10  *
11  * Contributors:
12  *     IBM Corporation - initial API and implementation
13  *     Markus Schorn (Wind River) - [108066] Project prefs marked dirty on read
14  *     James Blackburn (Broadcom Corp.) - ongoing development
15  *     Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427, 483529
16  *******************************************************************************/
17 package org.eclipse.core.internal.resources;
18 
19 import java.io.*;
20 import java.text.MessageFormat;
21 import java.util.*;
22 import org.eclipse.core.internal.preferences.*;
23 import org.eclipse.core.internal.utils.*;
24 import org.eclipse.core.resources.*;
25 import org.eclipse.core.runtime.*;
26 import org.eclipse.core.runtime.jobs.ISchedulingRule;
27 import org.eclipse.core.runtime.jobs.MultiRule;
28 import org.eclipse.core.runtime.preferences.IEclipsePreferences;
29 import org.eclipse.core.runtime.preferences.IExportedPreferences;
30 import org.eclipse.osgi.util.NLS;
31 import org.osgi.service.prefs.BackingStoreException;
32 import org.osgi.service.prefs.Preferences;
33 
34 /**
35  * Represents a node in the Eclipse preference hierarchy which stores preference
36  * values for projects.
37  *
38  * @since 3.0
39  */
40 public class ProjectPreferences extends EclipsePreferences {
41 	static final String PREFS_REGULAR_QUALIFIER = ResourcesPlugin.PI_RESOURCES;
42 	static final String PREFS_DERIVED_QUALIFIER = PREFS_REGULAR_QUALIFIER + ".derived"; //$NON-NLS-1$
43 	/**
44 	 * Cache which nodes have been loaded from disk
45 	 */
46 	protected static Set<String> loadedNodes = Collections.synchronizedSet(new HashSet<String>());
47 	private IFile file;
48 	private boolean initialized = false;
49 	/**
50 	 * Flag indicating that this node is currently reading values from disk,
51 	 * to avoid flushing during a read.
52 	 */
53 	private boolean isReading;
54 	/**
55 	 * Flag indicating that this node is currently writing values to disk,
56 	 * to avoid re-reading after the write completes.
57 	 */
58 	private boolean isWriting;
59 	private IEclipsePreferences loadLevel;
60 	private IProject project;
61 	private String qualifier;
62 
63 	// cache
64 	private int segmentCount;
65 
deleted(IFile file)66 	static void deleted(IFile file) throws CoreException {
67 		IPath path = file.getFullPath();
68 		int count = path.segmentCount();
69 		if (count != 3)
70 			return;
71 		// check if we are in the .settings directory
72 		if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1)))
73 			return;
74 		Preferences root = Platform.getPreferencesService().getRootNode();
75 		String project = path.segment(0);
76 		String qualifier = path.removeFileExtension().lastSegment();
77 		ProjectPreferences projectNode = (ProjectPreferences) root.node(ProjectScope.SCOPE).node(project);
78 		// if the node isn't known then just return
79 		try {
80 			if (!projectNode.nodeExists(qualifier))
81 				return;
82 		} catch (BackingStoreException e) {
83 			// ignore
84 		}
85 
86 		// clear the preferences
87 		clearNode(projectNode.node(qualifier));
88 
89 		// notifies the CharsetManager if needed
90 		if (qualifier.equals(PREFS_REGULAR_QUALIFIER) || qualifier.equals(PREFS_DERIVED_QUALIFIER))
91 			preferencesChanged(file.getProject());
92 	}
93 
deleted(IFolder folder)94 	static void deleted(IFolder folder) throws CoreException {
95 		IPath path = folder.getFullPath();
96 		int count = path.segmentCount();
97 		if (count != 2)
98 			return;
99 		// check if we are the .settings directory
100 		if (!EclipsePreferences.DEFAULT_PREFERENCES_DIRNAME.equals(path.segment(1)))
101 			return;
102 		Preferences root = Platform.getPreferencesService().getRootNode();
103 		// The settings dir has been removed/moved so remove all project prefs
104 		// for the resource.
105 		String project = path.segment(0);
106 		Preferences projectNode = root.node(ProjectScope.SCOPE).node(project);
107 		// check if we need to notify the charset manager
108 		boolean hasResourcesSettings = getFile(folder, PREFS_REGULAR_QUALIFIER).exists() || getFile(folder, PREFS_DERIVED_QUALIFIER).exists();
109 		// remove the preferences
110 		removeNode(projectNode);
111 		// notifies the CharsetManager
112 		if (hasResourcesSettings)
113 			preferencesChanged(folder.getProject());
114 	}
115 
116 	/*
117 	 * The whole project has been removed so delete all of the project settings
118 	 */
deleted(IProject project)119 	static void deleted(IProject project) throws CoreException {
120 		// The settings dir has been removed/moved so remove all project prefs
121 		// for the resource. We have to do this now because (since we aren't
122 		// synchronizing) there is short-circuit code that doesn't visit the
123 		// children.
124 		Preferences root = Platform.getPreferencesService().getRootNode();
125 		Preferences projectNode = root.node(ProjectScope.SCOPE).node(project.getName());
126 		// check if we need to notify the charset manager
127 		boolean hasResourcesSettings = getFile(project, PREFS_REGULAR_QUALIFIER).exists() || getFile(project, PREFS_DERIVED_QUALIFIER).exists();
128 		// remove the preferences
129 		removeNode(projectNode);
130 		// notifies the CharsetManager
131 		if (hasResourcesSettings)
132 			preferencesChanged(project);
133 	}
134 
deleted(IResource resource)135 	static void deleted(IResource resource) throws CoreException {
136 		switch (resource.getType()) {
137 			case IResource.FILE :
138 				deleted((IFile) resource);
139 				return;
140 			case IResource.FOLDER :
141 				deleted((IFolder) resource);
142 				return;
143 			case IResource.PROJECT :
144 				deleted((IProject) resource);
145 				return;
146 		}
147 	}
148 
149 	/*
150 	 * Return the preferences file for the given folder and qualifier.
151 	 */
getFile(IFolder folder, String qualifier)152 	static IFile getFile(IFolder folder, String qualifier) {
153 		Assert.isLegal(folder.getName().equals(DEFAULT_PREFERENCES_DIRNAME));
154 		return folder.getFile(new Path(qualifier).addFileExtension(PREFS_FILE_EXTENSION));
155 	}
156 
157 	/*
158 	 * Return the preferences file for the given project and qualifier.
159 	 */
getFile(IProject project, String qualifier)160 	static IFile getFile(IProject project, String qualifier) {
161 		return project.getFile(new Path(DEFAULT_PREFERENCES_DIRNAME).append(qualifier).addFileExtension(PREFS_FILE_EXTENSION));
162 	}
163 
loadProperties(IFile file)164 	private static Properties loadProperties(IFile file) throws BackingStoreException {
165 		if (Policy.DEBUG_PREFERENCES)
166 			Policy.debug("Loading preferences from file: " + file.getFullPath()); //$NON-NLS-1$
167 		Properties result = new Properties();
168 		try (
169 			InputStream input = new BufferedInputStream(file.getContents(true));
170 		) {
171 			result.load(input);
172 		} catch (CoreException e) {
173 			if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
174 				if (Policy.DEBUG_PREFERENCES)
175 					Policy.debug(MessageFormat.format("Preference file {0} does not exist.", file.getFullPath())); //$NON-NLS-1$
176 			} else {
177 				String message = NLS.bind(Messages.preferences_loadException, file.getFullPath());
178 				log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e));
179 				throw new BackingStoreException(message);
180 			}
181 		} catch (IOException e) {
182 			String message = NLS.bind(Messages.preferences_loadException, file.getFullPath());
183 			log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e));
184 			throw new BackingStoreException(message);
185 		}
186 		return result;
187 	}
188 
preferencesChanged(IProject project)189 	private static void preferencesChanged(IProject project) {
190 		Workspace workspace = ((Workspace) ResourcesPlugin.getWorkspace());
191 		workspace.getCharsetManager().projectPreferencesChanged(project);
192 		workspace.getContentDescriptionManager().projectPreferencesChanged(project);
193 	}
194 
read(ProjectPreferences node, IFile file)195 	private static void read(ProjectPreferences node, IFile file) throws BackingStoreException, CoreException {
196 		if (file == null || !file.exists()) {
197 			if (Policy.DEBUG_PREFERENCES)
198 				Policy.debug("Unable to determine preference file or file does not exist for node: " + node.absolutePath()); //$NON-NLS-1$
199 			return;
200 		}
201 		Properties fromDisk = loadProperties(file);
202 		// no work to do
203 		if (fromDisk.isEmpty())
204 			return;
205 		// create a new node to store the preferences in.
206 		IExportedPreferences myNode = (IExportedPreferences) ExportedPreferences.newRoot().node(node.absolutePath());
207 		convertFromProperties((EclipsePreferences) myNode, fromDisk, false);
208 		//flag that we are currently reading, to avoid unnecessary writing
209 		boolean oldIsReading = node.isReading;
210 		node.isReading = true;
211 		try {
212 			Platform.getPreferencesService().applyPreferences(myNode);
213 		} finally {
214 			node.isReading = oldIsReading;
215 		}
216 	}
217 
removeNode(Preferences node)218 	static void removeNode(Preferences node) throws CoreException {
219 		String message = NLS.bind(Messages.preferences_removeNodeException, node.absolutePath());
220 		try {
221 			node.removeNode();
222 		} catch (BackingStoreException e) {
223 			IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e);
224 			throw new CoreException(status);
225 		}
226 		removeLoadedNodes(node);
227 	}
228 
clearNode(Preferences node)229 	static void clearNode(Preferences node) throws CoreException {
230 		// if the underlying properties file was deleted, clear the values and remove
231 		// it from the list of loaded nodes, keep the node as it might still be referenced
232 		try {
233 			clearAll(node);
234 		} catch (BackingStoreException e) {
235 			String message = NLS.bind(Messages.preferences_clearNodeException, node.absolutePath());
236 			IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e);
237 			throw new CoreException(status);
238 		}
239 		removeLoadedNodes(node);
240 	}
241 
clearAll(Preferences node)242 	private static void clearAll(Preferences node) throws BackingStoreException {
243 		node.clear();
244 		String[] names = node.childrenNames();
245 		for (String name2 : names) {
246 			clearAll(node.node(name2));
247 		}
248 	}
249 
removeLoadedNodes(Preferences node)250 	private static void removeLoadedNodes(Preferences node) {
251 		String path = node.absolutePath();
252 		synchronized (loadedNodes) {
253 			for (Iterator<String> i = loadedNodes.iterator(); i.hasNext();) {
254 				String key = i.next();
255 				if (key.startsWith(path))
256 					i.remove();
257 			}
258 		}
259 	}
260 
updatePreferences(IFile file)261 	public static void updatePreferences(IFile file) throws CoreException {
262 		IPath path = file.getFullPath();
263 		// if we made it this far we are inside /project/.settings and might
264 		// have a change to a preference file
265 		if (!PREFS_FILE_EXTENSION.equals(path.getFileExtension()))
266 			return;
267 
268 		String project = path.segment(0);
269 		String qualifier = path.removeFileExtension().lastSegment();
270 		Preferences root = Platform.getPreferencesService().getRootNode();
271 		Preferences node = root.node(ProjectScope.SCOPE).node(project).node(qualifier);
272 		String message = null;
273 		try {
274 			message = NLS.bind(Messages.preferences_syncException, node.absolutePath());
275 			if (!(node instanceof ProjectPreferences))
276 				return;
277 			ProjectPreferences projectPrefs = (ProjectPreferences) node;
278 			if (projectPrefs.isWriting)
279 				return;
280 			read(projectPrefs, file);
281 			// Bug 108066: In case the node had existed before it was updated from
282 			// file, the read() operation marks it dirty. Override the dirty flag
283 			// since we know that the node is expected to be in sync with the file.
284 			projectPrefs.dirty = false;
285 
286 			// make sure that we generate the appropriate resource change events
287 			// if encoding settings have changed
288 			if (PREFS_REGULAR_QUALIFIER.equals(qualifier) || PREFS_DERIVED_QUALIFIER.equals(qualifier))
289 				preferencesChanged(file.getProject());
290 		} catch (BackingStoreException e) {
291 			IStatus status = new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e);
292 			throw new CoreException(status);
293 		}
294 	}
295 
296 	/**
297 	 * Default constructor. Should only be called by #createExecutableExtension.
298 	 */
ProjectPreferences()299 	public ProjectPreferences() {
300 		super(null, null);
301 	}
302 
ProjectPreferences(EclipsePreferences parent, String name)303 	private ProjectPreferences(EclipsePreferences parent, String name) {
304 		super(parent, name);
305 
306 		// cache the segment count
307 		String path = absolutePath();
308 		segmentCount = getSegmentCount(path);
309 
310 		if (segmentCount == 1)
311 			return;
312 
313 		// cache the project name
314 		String projectName = getSegment(path, 1);
315 		if (projectName != null)
316 			project = ResourcesPlugin.getWorkspace().getRoot().getProject(projectName);
317 
318 		// cache the qualifier
319 		if (segmentCount > 2)
320 			qualifier = getSegment(path, 2);
321 	}
322 
323 	@Override
childrenNames()324 	public String[] childrenNames() throws BackingStoreException {
325 		// illegal state if this node has been removed
326 		checkRemoved();
327 		initialize();
328 		silentLoad();
329 		return super.childrenNames();
330 	}
331 
332 	@Override
clear()333 	public void clear() {
334 		// illegal state if this node has been removed
335 		checkRemoved();
336 		silentLoad();
337 		super.clear();
338 	}
339 
340 	/*
341 	 * Figure out what the children of this node are based on the resources
342 	 * that are in the workspace.
343 	 */
computeChildren()344 	private String[] computeChildren() {
345 		if (project == null)
346 			return EMPTY_STRING_ARRAY;
347 		IFolder folder = project.getFolder(DEFAULT_PREFERENCES_DIRNAME);
348 		if (!folder.exists())
349 			return EMPTY_STRING_ARRAY;
350 		IResource[] members = null;
351 		try {
352 			members = folder.members();
353 		} catch (CoreException e) {
354 			return EMPTY_STRING_ARRAY;
355 		}
356 		ArrayList<String> result = new ArrayList<>();
357 		for (IResource resource : members) {
358 			if (resource.getType() == IResource.FILE && PREFS_FILE_EXTENSION.equals(resource.getFullPath().getFileExtension()))
359 				result.add(resource.getFullPath().removeFileExtension().lastSegment());
360 		}
361 		return result.toArray(EMPTY_STRING_ARRAY);
362 	}
363 
364 	@Override
flush()365 	public void flush() throws BackingStoreException {
366 		if (isReading)
367 			return;
368 		isWriting = true;
369 		try {
370 			// call the internal method because we don't want to be synchronized, we will do that ourselves later.
371 			IEclipsePreferences toFlush = super.internalFlush();
372 			//if we aren't at the right level, then flush the appropriate node
373 			if (toFlush != null)
374 				toFlush.flush();
375 		} finally {
376 			isWriting = false;
377 		}
378 	}
379 
getFile()380 	private IFile getFile() {
381 		if (file == null) {
382 			if (project == null || qualifier == null)
383 				return null;
384 			file = getFile(project, qualifier);
385 		}
386 		return file;
387 	}
388 
389 	/*
390 	 * Return the node at which these preferences are loaded/saved.
391 	 */
392 	@Override
getLoadLevel()393 	protected IEclipsePreferences getLoadLevel() {
394 		if (loadLevel == null) {
395 			if (project == null || qualifier == null)
396 				return null;
397 			// Make it relative to this node rather than navigating to it from the root.
398 			// Walk backwards up the tree starting at this node.
399 			// This is important to avoid a chicken/egg thing on startup.
400 			EclipsePreferences node = this;
401 			for (int i = 3; i < segmentCount; i++)
402 				node = (EclipsePreferences) node.parent();
403 			loadLevel = node;
404 		}
405 		return loadLevel;
406 	}
407 
408 	/*
409 	 * Calculate and return the file system location for this preference node.
410 	 * Use the absolute path of the node to find out the project name so
411 	 * we can get its location on disk.
412 	 *
413 	 * NOTE: we cannot cache the location since it may change over the course
414 	 * of the project life-cycle.
415 	 */
416 	@Override
getLocation()417 	protected IPath getLocation() {
418 		if (project == null || qualifier == null)
419 			return null;
420 		IPath path = project.getLocation();
421 		return computeLocation(path, qualifier);
422 	}
423 
424 	@Override
internalCreate(EclipsePreferences nodeParent, String nodeName, Object context)425 	protected EclipsePreferences internalCreate(EclipsePreferences nodeParent, String nodeName, Object context) {
426 		return new ProjectPreferences(nodeParent, nodeName);
427 	}
428 
429 	@Override
internalGet(String key)430 	protected String internalGet(String key) {
431 		// throw NPE if key is null
432 		if (key == null)
433 			throw new NullPointerException();
434 		// illegal state if this node has been removed
435 		checkRemoved();
436 		silentLoad();
437 		return super.internalGet(key);
438 	}
439 
440 	@Override
internalPut(String key, String newValue)441 	protected String internalPut(String key, String newValue) {
442 		// illegal state if this node has been removed
443 		checkRemoved();
444 		silentLoad();
445 		if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) {
446 			if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) {
447 				CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager();
448 				if (Boolean.parseBoolean(newValue))
449 					charsetManager.splitEncodingPreferences(project);
450 				else
451 					charsetManager.mergeEncodingPreferences(project);
452 			}
453 		}
454 		return super.internalPut(key, newValue);
455 	}
456 
initialize()457 	private void initialize() {
458 		if (segmentCount != 2)
459 			return;
460 
461 		// if already initialized, then skip this initialization
462 		if (initialized)
463 			return;
464 
465 		// initialize the children only if project is opened
466 		if (project.isOpen()) {
467 			try {
468 				synchronized (this) {
469 					List<String> addedNames = Arrays.asList(internalChildNames());
470 					String[] names = computeChildren();
471 					// add names only for nodes that were not added previously
472 					for (String name : names) {
473 						if (!addedNames.contains(name)) {
474 							addChild(name, null);
475 						}
476 					}
477 				}
478 			} finally {
479 				// mark as initialized so that subsequent project opening will not initialize preferences again
480 				initialized = true;
481 			}
482 		}
483 	}
484 
485 	@Override
isAlreadyLoaded(IEclipsePreferences node)486 	protected boolean isAlreadyLoaded(IEclipsePreferences node) {
487 		return loadedNodes.contains(node.absolutePath());
488 	}
489 
490 	@Override
keys()491 	public String[] keys() {
492 		// illegal state if this node has been removed
493 		checkRemoved();
494 		silentLoad();
495 		return super.keys();
496 	}
497 
498 	@Override
load()499 	protected void load() throws BackingStoreException {
500 		load(true);
501 	}
502 
load(boolean reportProblems)503 	private void load(boolean reportProblems) throws BackingStoreException {
504 		IFile localFile = getFile();
505 		if (localFile == null || !localFile.exists()) {
506 			if (Policy.DEBUG_PREFERENCES)
507 				Policy.debug("Unable to determine preference file or file does not exist for node: " + absolutePath()); //$NON-NLS-1$
508 			return;
509 		}
510 		if (Policy.DEBUG_PREFERENCES)
511 			Policy.debug("Loading preferences from file: " + localFile.getFullPath()); //$NON-NLS-1$
512 		Properties fromDisk = new Properties();
513 		try (
514 			InputStream input = new BufferedInputStream(localFile.getContents(true));
515 		) {
516 			fromDisk.load(input);
517 			convertFromProperties(this, fromDisk, true);
518 			loadedNodes.add(absolutePath());
519 		} catch (CoreException e) {
520 			if (e.getStatus().getCode() == IResourceStatus.RESOURCE_NOT_FOUND) {
521 				if (Policy.DEBUG_PREFERENCES)
522 					Policy.debug("Preference file does not exist for node: " + absolutePath()); //$NON-NLS-1$
523 				return;
524 			}
525 			if (reportProblems) {
526 				String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath());
527 				log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e));
528 				throw new BackingStoreException(message);
529 			}
530 		} catch (IOException e) {
531 			if (reportProblems) {
532 				String message = NLS.bind(Messages.preferences_loadException, localFile.getFullPath());
533 				log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e));
534 				throw new BackingStoreException(message);
535 			}
536 		}
537 	}
538 
539 	/**
540 	 * If we are at the /project node and we are checking for the existence of a child, we
541 	 * want special behaviour. If the child is a single segment name, then we want to
542 	 * return true if the node exists OR if a project with that name exists in the workspace.
543 	 */
544 	@Override
nodeExists(String path)545 	public boolean nodeExists(String path) throws BackingStoreException {
546 		// short circuit for checking this node
547 		if (path.length() == 0)
548 			return !removed;
549 		// illegal state if this node has been removed.
550 		// do this AFTER checking for the empty string.
551 		checkRemoved();
552 		initialize();
553 		silentLoad();
554 		if (segmentCount != 1)
555 			return super.nodeExists(path);
556 		if (path.length() == 0)
557 			return super.nodeExists(path);
558 		if (path.charAt(0) == IPath.SEPARATOR)
559 			return super.nodeExists(path);
560 		if (path.indexOf(IPath.SEPARATOR) != -1)
561 			return super.nodeExists(path);
562 		// if we are checking existance of a single segment child of /project, base the answer on
563 		// whether or not it exists in the workspace.
564 		return ResourcesPlugin.getWorkspace().getRoot().getProject(path).exists() || super.nodeExists(path);
565 	}
566 
567 	@Override
remove(String key)568 	public void remove(String key) {
569 		// illegal state if this node has been removed
570 		checkRemoved();
571 		silentLoad();
572 		super.remove(key);
573 		if ((segmentCount == 3) && PREFS_REGULAR_QUALIFIER.equals(qualifier) && (project != null)) {
574 			if (ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS.equals(key)) {
575 				CharsetManager charsetManager = ((Workspace) ResourcesPlugin.getWorkspace()).getCharsetManager();
576 				if (ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS)
577 					charsetManager.splitEncodingPreferences(project);
578 				else
579 					charsetManager.mergeEncodingPreferences(project);
580 			}
581 		}
582 	}
583 
584 	@Override
save()585 	protected void save() throws BackingStoreException {
586 		final IFile fileInWorkspace = getFile();
587 		if (fileInWorkspace == null) {
588 			if (Policy.DEBUG_PREFERENCES)
589 				Policy.debug("Not saving preferences since there is no file for node: " + absolutePath()); //$NON-NLS-1$
590 			return;
591 		}
592 		final String finalQualifier = qualifier;
593 		final BackingStoreException[] bse = new BackingStoreException[1];
594 		try {
595 			ICoreRunnable operation = monitor -> {
596 				try {
597 					Properties table = convertToProperties(new SortedProperties(), ""); //$NON-NLS-1$
598 					// nothing to save. delete existing file if one exists.
599 					if (table.isEmpty()) {
600 						if (fileInWorkspace.exists()) {
601 							if (Policy.DEBUG_PREFERENCES)
602 								Policy.debug("Deleting preference file: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$
603 							if (fileInWorkspace.isReadOnly()) {
604 								IStatus status1 = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT);
605 								if (!status1.isOK())
606 									throw new CoreException(status1);
607 							}
608 							try {
609 								fileInWorkspace.delete(true, null);
610 							} catch (CoreException e1) {
611 								String message1 = NLS.bind(Messages.preferences_deleteException, fileInWorkspace.getFullPath());
612 								log(new Status(IStatus.WARNING, ResourcesPlugin.PI_RESOURCES, IStatus.WARNING, message1, null));
613 							}
614 						}
615 						return;
616 					}
617 					table.put(VERSION_KEY, VERSION_VALUE);
618 					// print the table to a string and remove the timestamp that Properties#store always adds
619 					String s = removeTimestampFromTable(table);
620 					String systemLineSeparator = System.lineSeparator();
621 					String fileLineSeparator = FileUtil.getLineSeparator(fileInWorkspace);
622 					if (!systemLineSeparator.equals(fileLineSeparator))
623 						s = s.replaceAll(systemLineSeparator, fileLineSeparator);
624 					InputStream input = new BufferedInputStream(new ByteArrayInputStream(s.getBytes("UTF-8"))); //$NON-NLS-1$
625 					// make sure that preference folder and file are in sync
626 					fileInWorkspace.getParent().refreshLocal(IResource.DEPTH_ZERO, null);
627 					fileInWorkspace.refreshLocal(IResource.DEPTH_ZERO, null);
628 					if (fileInWorkspace.exists()) {
629 						if (Policy.DEBUG_PREFERENCES)
630 							Policy.debug("Setting preference file contents for: " + fileInWorkspace.getFullPath()); //$NON-NLS-1$
631 						if (fileInWorkspace.isReadOnly()) {
632 							IStatus status2 = fileInWorkspace.getWorkspace().validateEdit(new IFile[] {fileInWorkspace}, IWorkspace.VALIDATE_PROMPT);
633 							if (!status2.isOK()) {
634 								input.close();
635 								throw new CoreException(status2);
636 							}
637 						}
638 						// set the contents
639 						fileInWorkspace.setContents(input, IResource.KEEP_HISTORY, null);
640 					} else {
641 						// create the file
642 						IFolder folder = (IFolder) fileInWorkspace.getParent();
643 						if (!folder.exists()) {
644 							if (Policy.DEBUG_PREFERENCES)
645 								Policy.debug("Creating parent preference directory: " + folder.getFullPath()); //$NON-NLS-1$
646 							folder.create(IResource.NONE, true, null);
647 						}
648 						if (Policy.DEBUG_PREFERENCES)
649 							Policy.debug("Creating preference file: " + fileInWorkspace.getLocation()); //$NON-NLS-1$
650 						fileInWorkspace.create(input, IResource.NONE, null);
651 					}
652 					if (PREFS_DERIVED_QUALIFIER.equals(finalQualifier))
653 						fileInWorkspace.setDerived(true, null);
654 				} catch (BackingStoreException e2) {
655 					bse[0] = e2;
656 				} catch (IOException e3) {
657 					String message2 = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath());
658 					log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message2, e3));
659 					bse[0] = new BackingStoreException(message2);
660 				}
661 			};
662 			//don't bother with scheduling rules if we are already inside an operation
663 			try {
664 				IWorkspace workspace = ResourcesPlugin.getWorkspace();
665 				if (((Workspace) workspace).getWorkManager().isLockAlreadyAcquired()) {
666 					operation.run(null);
667 				} else {
668 					IResourceRuleFactory factory = workspace.getRuleFactory();
669 					// we might: delete the file, create the .settings folder, create the file, modify the file, or set derived flag for the file.
670 					ISchedulingRule rule = MultiRule.combine(new ISchedulingRule[] {factory.deleteRule(fileInWorkspace), factory.createRule(fileInWorkspace.getParent()), factory.modifyRule(fileInWorkspace), factory.derivedRule(fileInWorkspace)});
671 					workspace.run(operation, rule, IResource.NONE, null);
672 					if (bse[0] != null)
673 						throw bse[0];
674 				}
675 			} catch (OperationCanceledException e) {
676 				throw new BackingStoreException(Messages.preferences_operationCanceled);
677 			}
678 		} catch (CoreException e) {
679 			String message = NLS.bind(Messages.preferences_saveProblems, fileInWorkspace.getFullPath());
680 			log(new Status(IStatus.ERROR, ResourcesPlugin.PI_RESOURCES, IStatus.ERROR, message, e));
681 			throw new BackingStoreException(message);
682 		}
683 	}
684 
silentLoad()685 	private void silentLoad() {
686 		ProjectPreferences node = (ProjectPreferences) getLoadLevel();
687 		if (node == null)
688 			return;
689 		if (isAlreadyLoaded(node) || node.isLoading())
690 			return;
691 		try {
692 			node.setLoading(true);
693 			node.load(false);
694 		} catch (BackingStoreException e) {
695 			// will not happen, all exceptions are swallowed by load(false)
696 		} finally {
697 			node.setLoading(false);
698 		}
699 	}
700 }
701