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