platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java
changeset 40 eb3c938c7fef
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/platform35/org.eclipse.core.resources/src/org/eclipse/core/internal/resources/CharsetManager.java	Thu Jul 30 11:56:23 2009 -0500
@@ -0,0 +1,304 @@
+/*******************************************************************************
+ * Copyright (c) 2004, 2006 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.resources;
+
+import java.util.*;
+import org.eclipse.core.internal.utils.Messages;
+import org.eclipse.core.internal.utils.Policy;
+import org.eclipse.core.resources.*;
+import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.jobs.ISchedulingRule;
+import org.eclipse.core.runtime.jobs.Job;
+import org.osgi.framework.Bundle;
+import org.osgi.service.prefs.BackingStoreException;
+import org.osgi.service.prefs.Preferences;
+
+/**
+ * Manages user-defined encodings as preferences in the project content area.
+ * 
+ * @since 3.0
+ */
+public class CharsetManager implements IManager {
+	/**
+	 * This job implementation is used to allow the resource change listener
+	 * to schedule operations that need to modify the workspace. 
+	 */
+	private class CharsetManagerJob extends Job {
+		private static final int CHARSET_UPDATE_DELAY = 500;
+		private List asyncChanges = new ArrayList();
+
+		public CharsetManagerJob() {
+			super(Messages.resources_charsetUpdating);
+			setSystem(true);
+			setPriority(Job.INTERACTIVE);
+		}
+
+		public void addChanges(Set newChanges) {
+			if (newChanges.isEmpty())
+				return;
+			synchronized (asyncChanges) {
+				asyncChanges.addAll(newChanges);
+				asyncChanges.notify();
+			}
+			schedule(CHARSET_UPDATE_DELAY);
+		}
+
+		public IProject getNextChange() {
+			synchronized (asyncChanges) {
+				return asyncChanges.isEmpty() ? null : (IProject) asyncChanges.remove(asyncChanges.size() - 1);
+			}
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.core.internal.jobs.InternalJob#run(org.eclipse.core.runtime.IProgressMonitor)
+		 */
+		protected IStatus run(IProgressMonitor monitor) {
+			MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.FAILED_SETTING_CHARSET, Messages.resources_updatingEncoding, null);
+			monitor = Policy.monitorFor(monitor);
+			try {
+				monitor.beginTask(Messages.resources_charsetUpdating, Policy.totalWork);
+				final ISchedulingRule rule = workspace.getRuleFactory().modifyRule(workspace.getRoot());
+				try {
+					workspace.prepareOperation(rule, monitor);
+					workspace.beginOperation(true);
+					IProject next;
+					while ((next = getNextChange()) != null) {
+						//just exit if the system is shutting down or has been shut down
+						//it is too late to change the workspace at this point anyway
+						if (systemBundle.getState() != Bundle.ACTIVE)
+							return Status.OK_STATUS;
+						try {
+							if (next.isAccessible()) {
+								Preferences projectPrefs = getPreferences(next, false);
+								if (projectPrefs != null)
+									projectPrefs.flush();
+							}
+						} catch (BackingStoreException e) {
+							// we got an error saving					
+							String detailMessage = Messages.resources_savingEncoding;
+							result.add(new ResourceStatus(IResourceStatus.FAILED_SETTING_CHARSET, next.getFullPath(), detailMessage, e));
+						}
+					}
+					monitor.worked(Policy.opWork);
+				} catch (OperationCanceledException e) {
+					workspace.getWorkManager().operationCanceled();
+					throw e;
+				} finally {
+					workspace.endOperation(rule, true, Policy.subMonitorFor(monitor, Policy.endOpWork));
+				}
+			} catch (CoreException ce) {
+				return ce.getStatus();
+			} finally {
+				monitor.done();
+			}
+			return result;
+		}
+
+		/* (non-Javadoc)
+		 * @see org.eclipse.core.runtime.jobs.Job#shouldRun()
+		 */
+		public boolean shouldRun() {
+			synchronized (asyncChanges) {
+				return !asyncChanges.isEmpty();
+			}
+		}
+	}
+
+	class Listener implements IResourceChangeListener {
+
+		private void processEntryChanges(IResourceDelta projectDelta, Set projectsToSave) {
+			// check each resource with user-set encoding to see if it has
+			// been moved/deleted
+			boolean resourceChanges = false;
+			IProject currentProject = (IProject) projectDelta.getResource();
+			Preferences projectPrefs = getPreferences(currentProject, false);
+			if (projectPrefs == null)
+				// no preferences for this project, just bail
+				return;
+			String[] affectedResources;
+			try {
+				affectedResources = projectPrefs.keys();
+			} catch (BackingStoreException e) {
+				// problems with the project scope... we gonna miss the changes (but will log)
+				String message = Messages.resources_readingEncoding;
+				Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, currentProject.getFullPath(), message, e));
+				return;
+			}
+			for (int i = 0; i < affectedResources.length; i++) {
+				IResourceDelta memberDelta = projectDelta.findMember(new Path(affectedResources[i]));
+				// no changes for the given resource
+				if (memberDelta == null)
+					continue;
+				if (memberDelta.getKind() == IResourceDelta.REMOVED) {
+					resourceChanges = true;
+					// remove the setting for the original location - save its value though
+					String currentValue = projectPrefs.get(affectedResources[i], null);
+					projectPrefs.remove(affectedResources[i]);
+					if ((memberDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
+						// if moving, copy the setting for the new location
+						IProject targetProject = workspace.getRoot().getProject(memberDelta.getMovedToPath().segment(0));
+						Preferences targetPrefs = getPreferences(targetProject, true);
+						targetPrefs.put(getKeyFor(memberDelta.getMovedToPath()), currentValue);
+						if (targetProject != currentProject)
+							projectsToSave.add(targetProject);
+					}
+				}
+			}
+			if (resourceChanges)
+				projectsToSave.add(currentProject);
+		}
+
+		/**
+		 * For any change to the encoding file or any resource with encoding
+		 * set, just discard the cache for the corresponding project.
+		 */
+		public void resourceChanged(IResourceChangeEvent event) {
+			IResourceDelta delta = event.getDelta();
+			if (delta == null)
+				return;
+			IResourceDelta[] projectDeltas = delta.getAffectedChildren();
+			// process each project in the delta
+			Set projectsToSave = new HashSet();
+			for (int i = 0; i < projectDeltas.length; i++)
+				//nothing to do if a project has been added/removed/moved				
+				if (projectDeltas[i].getKind() == IResourceDelta.CHANGED && (projectDeltas[i].getFlags() & IResourceDelta.OPEN) == 0)
+					processEntryChanges(projectDeltas[i], projectsToSave);
+			job.addChanges(projectsToSave);
+		}
+	}
+
+	public static final String ENCODING_PREF_NODE = "encoding"; //$NON-NLS-1$		
+	private static final String PROJECT_KEY = "<project>"; //$NON-NLS-1$
+	private CharsetDeltaJob charsetListener;
+	CharsetManagerJob job;
+	private IResourceChangeListener listener;
+	protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$
+	Workspace workspace;
+
+	public CharsetManager(Workspace workspace) {
+		this.workspace = workspace;
+	}
+
+	/**
+	 * Returns the charset explicitly set by the user for the given resource, 
+	 * or <code>null</code>. If no setting exists for the given resource and 
+	 * <code>recurse</code> is <code>true</code>, every parent up to the 
+	 * workspace root will be checked until a charset setting can be found.
+	 * 
+	 * @param resourcePath the path for the resource
+	 * @param recurse whether the parent should be queried
+	 * @return the charset setting for the given resource
+	 */
+	public String getCharsetFor(IPath resourcePath, boolean recurse) {
+		Assert.isLegal(resourcePath.segmentCount() >= 1);
+		IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+		Preferences encodingSettings = getPreferences(project, false);
+		if (encodingSettings == null)
+			// no preferences found - for performance reasons, short-circuit 
+			// lookup by falling back to workspace's default setting			
+			return recurse ? ResourcesPlugin.getEncoding() : null;
+		return internalGetCharsetFor(resourcePath, encodingSettings, recurse);
+	}
+
+	String getKeyFor(IPath resourcePath) {
+		return resourcePath.segmentCount() > 1 ? resourcePath.removeFirstSegments(1).toString() : PROJECT_KEY;
+	}
+
+	Preferences getPreferences(IProject project, boolean create) {
+		if (create)
+			// create all nodes down to the one we are interested in
+			return new ProjectScope(project).getNode(ResourcesPlugin.PI_RESOURCES).node(ENCODING_PREF_NODE);
+		// be careful looking up for our node so not to create any nodes as side effect
+		Preferences node = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
+		try {
+			//TODO once bug 90500 is fixed, should be as simple as this:
+			//			String path = project.getName() + IPath.SEPARATOR + ResourcesPlugin.PI_RESOURCES + IPath.SEPARATOR + ENCODING_PREF_NODE;
+			//			return node.nodeExists(path) ? node.node(path) : null;
+			// for now, take the long way
+			if (!node.nodeExists(project.getName()))
+				return null;
+			node = node.node(project.getName());
+			if (!node.nodeExists(ResourcesPlugin.PI_RESOURCES))
+				return null;
+			node = node.node(ResourcesPlugin.PI_RESOURCES);
+			if (!node.nodeExists(ENCODING_PREF_NODE))
+				return null;
+			return node.node(ENCODING_PREF_NODE);
+		} catch (BackingStoreException e) {
+			// nodeExists failed
+			String message = Messages.resources_readingEncoding;
+			Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
+		}
+		return null;
+	}
+
+	private String internalGetCharsetFor(IPath resourcePath, Preferences encodingSettings, boolean recurse) {
+		String charset = encodingSettings.get(getKeyFor(resourcePath), null);
+		if (!recurse)
+			return charset;
+		while (charset == null && resourcePath.segmentCount() > 1) {
+			resourcePath = resourcePath.removeLastSegments(1);
+			charset = encodingSettings.get(getKeyFor(resourcePath), null);
+		}
+		// ensure we default to the workspace encoding if none is found
+		return charset == null ? ResourcesPlugin.getEncoding() : charset;
+	}
+
+	public void projectPreferencesChanged(IProject project) {
+		charsetListener.charsetPreferencesChanged(project);
+	}
+
+	public void setCharsetFor(IPath resourcePath, String newCharset) throws CoreException {
+		// for the workspace root we just set a preference in the instance scope
+		if (resourcePath.segmentCount() == 0) {
+			org.eclipse.core.runtime.Preferences resourcesPreferences = ResourcesPlugin.getPlugin().getPluginPreferences();
+			if (newCharset != null)
+				resourcesPreferences.setValue(ResourcesPlugin.PREF_ENCODING, newCharset);
+			else
+				resourcesPreferences.setToDefault(ResourcesPlugin.PREF_ENCODING);
+			ResourcesPlugin.getPlugin().savePluginPreferences();
+			return;
+		}
+		// for all other cases, we set a property in the corresponding project
+		IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
+		Preferences encodingSettings = getPreferences(project, true);
+		if (newCharset == null || newCharset.trim().length() == 0)
+			encodingSettings.remove(getKeyFor(resourcePath));
+		else
+			encodingSettings.put(getKeyFor(resourcePath), newCharset);
+		try {
+			// disable the listener so we don't react to changes made by ourselves 
+			charsetListener.setDisabled(true);
+			// save changes
+			encodingSettings.flush();
+		} catch (BackingStoreException e) {
+			String message = Messages.resources_savingEncoding;
+			throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
+		} finally {
+			charsetListener.setDisabled(false);
+		}
+
+	}
+
+	public void shutdown(IProgressMonitor monitor) {
+		workspace.removeResourceChangeListener(listener);
+		if (charsetListener != null)
+			charsetListener.shutdown();
+	}
+
+	public void startup(IProgressMonitor monitor) {
+		job = new CharsetManagerJob();
+		listener = new Listener();
+		workspace.addResourceChangeListener(listener, IResourceChangeEvent.POST_CHANGE);
+		charsetListener = new CharsetDeltaJob(workspace);
+		charsetListener.startup();
+	}
+}