--- /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();
+ }
+}