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.eclipse.core.runtime.preferences.IEclipsePreferences;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.osgi.framework.Bundle;
import org.osgi.service.prefs.BackingStoreException;
import org.osgi.service.prefs.Preferences;
public class CharsetManager implements IManager {
private class CharsetManagerJob extends Job {
private static final int CHARSET_UPDATE_DELAY = 500;
private List<Map.Entry<IProject, Boolean>> asyncChanges = new ArrayList<>();
public CharsetManagerJob() {
super(Messages.resources_charsetUpdating);
setSystem(true);
setPriority(Job.INTERACTIVE);
}
@Override
public boolean belongsTo(Object family) {
return CharsetManager.class == family;
}
public void addChanges(Map<IProject, Boolean> newChanges) {
if (newChanges.isEmpty())
return;
synchronized (asyncChanges) {
asyncChanges.addAll(newChanges.entrySet());
asyncChanges.notify();
}
schedule(CHARSET_UPDATE_DELAY);
}
public Map.Entry<IProject, Boolean> getNextChange() {
synchronized (asyncChanges) {
return asyncChanges.isEmpty() ? null : asyncChanges.remove(asyncChanges.size() - 1);
}
}
@Override
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);
Map.Entry<IProject, Boolean> next;
while ((next = getNextChange()) != null) {
if (systemBundle.getState() != Bundle.ACTIVE)
return Status.OK_STATUS;
IProject project = next.getKey();
try {
if (project.isAccessible()) {
boolean shouldDisableCharsetDeltaJob = next.getValue().booleanValue();
flushPreferences(getPreferences(project, false, false, true), shouldDisableCharsetDeltaJob);
flushPreferences(getPreferences(project, false, true, true), shouldDisableCharsetDeltaJob);
}
} catch (BackingStoreException e) {
String detailMessage = Messages.resources_savingEncoding;
result.add(new ResourceStatus(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), detailMessage, e));
}
}
monitor.worked(Policy.opWork);
} catch (OperationCanceledException e) {
workspace.getWorkManager().operationCanceled();
throw e;
} finally {
workspace.endOperation(rule, true);
}
} catch (CoreException ce) {
return ce.getStatus();
} finally {
monitor.done();
}
return result;
}
@Override
public boolean shouldRun() {
synchronized (asyncChanges) {
return !asyncChanges.isEmpty();
}
}
}
private class ResourceChangeListener implements IResourceChangeListener {
public ResourceChangeListener() {
}
private boolean moveSettingsIfDerivedChanged(IResourceDelta parent, IProject currentProject, Preferences projectPrefs, String[] affectedResources) {
boolean resourceChanges = false;
if ((parent.getFlags() & IResourceDelta.DERIVED_CHANGED) != 0) {
IPath parentPath = parent.getResource().getProjectRelativePath();
for (String affectedResource : affectedResources) {
IPath affectedPath = new Path(affectedResource);
if (parentPath.isPrefixOf(affectedPath)) {
IResource member = currentProject.findMember(affectedPath);
if (member != null) {
Preferences targetPrefs = getPreferences(currentProject, true, member.isDerived(IResource.CHECK_ANCESTORS));
if (!projectPrefs.absolutePath().equals(targetPrefs.absolutePath())) {
String currentValue = projectPrefs.get(affectedResource, null);
projectPrefs.remove(affectedResource);
targetPrefs.put(affectedResource, currentValue);
resourceChanges = true;
}
}
}
}
}
for (IResourceDelta child : parent.getAffectedChildren()) {
resourceChanges = moveSettingsIfDerivedChanged(child, currentProject, projectPrefs, affectedResources) || resourceChanges;
}
return resourceChanges;
}
private void processEntryChanges(IResourceDelta projectDelta, Map<IProject, Boolean> projectsToSave) {
IProject currentProject = (IProject) projectDelta.getResource();
Preferences projectRegularPrefs = getPreferences(currentProject, false, false, true);
Preferences projectDerivedPrefs = getPreferences(currentProject, false, true, true);
Map<Boolean, String[]> affectedResourcesMap = new HashMap<>();
try {
if (projectRegularPrefs == null)
affectedResourcesMap.put(Boolean.FALSE, new String[0]);
else
affectedResourcesMap.put(Boolean.FALSE, projectRegularPrefs.keys());
if (projectDerivedPrefs == null)
affectedResourcesMap.put(Boolean.TRUE, new String[0]);
else
affectedResourcesMap.put(Boolean.TRUE, projectDerivedPrefs.keys());
} catch (BackingStoreException e) {
String message = Messages.resources_readingEncoding;
Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, currentProject.getFullPath(), message, e));
return;
}
for (Map.Entry<Boolean, String[]> entry : affectedResourcesMap.entrySet()) {
Boolean isDerived = entry.getKey();
String[] affectedResources = entry.getValue();
Preferences projectPrefs = isDerived.booleanValue() ? projectDerivedPrefs : projectRegularPrefs;
for (String affectedResource : affectedResources) {
IResourceDelta memberDelta = projectDelta.findMember(new Path(affectedResource));
if (memberDelta == null)
continue;
if (memberDelta.getKind() == IResourceDelta.REMOVED) {
boolean shouldDisableCharsetDeltaJobForCurrentProject = false;
String currentValue = projectPrefs.get(affectedResource, null);
projectPrefs.remove(affectedResource);
if ((memberDelta.getFlags() & IResourceDelta.MOVED_TO) != 0) {
IPath movedToPath = memberDelta.getMovedToPath();
IResource resource = workspace.getRoot().findMember(movedToPath);
if (resource != null) {
Preferences encodingSettings = getPreferences(resource.getProject(), true, resource.isDerived(IResource.CHECK_ANCESTORS));
if (currentValue == null || currentValue.trim().length() == 0)
encodingSettings.remove(getKeyFor(movedToPath));
else
encodingSettings.put(getKeyFor(movedToPath), currentValue);
IProject targetProject = workspace.getRoot().getProject(movedToPath.segment(0));
if (targetProject.equals(currentProject))
shouldDisableCharsetDeltaJobForCurrentProject = true;
else
projectsToSave.put(targetProject, Boolean.FALSE);
}
}
projectsToSave.put(currentProject, Boolean.valueOf(shouldDisableCharsetDeltaJobForCurrentProject));
}
}
if (moveSettingsIfDerivedChanged(projectDelta, currentProject, projectPrefs, affectedResources)) {
projectsToSave.put(currentProject, Boolean.TRUE);
}
}
}
@Override
public void resourceChanged(IResourceChangeEvent event) {
IResourceDelta delta = event.getDelta();
if (delta == null)
return;
IResourceDelta[] projectDeltas = delta.getAffectedChildren();
Map<IProject, Boolean> projectsToSave = new HashMap<>();
for (IResourceDelta projectDelta : projectDeltas)
if (projectDelta.getKind() == IResourceDelta.CHANGED && (projectDelta.getFlags() & IResourceDelta.OPEN) == 0)
processEntryChanges(projectDelta, projectsToSave);
job.addChanges(projectsToSave);
}
}
private static final String PROJECT_KEY = "<project>";
private CharsetDeltaJob charsetListener;
CharsetManagerJob job;
private IResourceChangeListener resourceChangeListener;
protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi");
Workspace workspace;
public CharsetManager(Workspace workspace) {
this.workspace = workspace;
}
void flushPreferences(Preferences projectPrefs, boolean shouldDisableCharsetDeltaJob) throws BackingStoreException {
if (projectPrefs != null) {
try {
if (shouldDisableCharsetDeltaJob)
charsetListener.setDisabled(true);
projectPrefs.flush();
} finally {
if (shouldDisableCharsetDeltaJob)
charsetListener.setDisabled(false);
}
}
}
public String getCharsetFor(IPath resourcePath, boolean recurse) {
Assert.isLegal(resourcePath.segmentCount() >= 1);
IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
Preferences prefs = getPreferences(project, false, false);
Preferences derivedPrefs = getPreferences(project, false, true);
if (prefs == null && derivedPrefs == null)
return recurse ? ResourcesPlugin.getEncoding() : null;
return internalGetCharsetFor(prefs, derivedPrefs, resourcePath, recurse);
}
static String getKeyFor(IPath resourcePath) {
return resourcePath.segmentCount() > 1 ? resourcePath.removeFirstSegments(1).toString() : PROJECT_KEY;
}
Preferences getPreferences(IProject project, boolean create, boolean isDerived) {
return getPreferences(project, create, isDerived, isDerivedEncodingStoredSeparately(project));
}
Preferences getPreferences(IProject project, boolean create, boolean isDerived, boolean isDerivedEncodingStoredSeparately) {
boolean localIsDerived = isDerivedEncodingStoredSeparately ? isDerived : false;
String qualifier = localIsDerived ? ProjectPreferences.PREFS_DERIVED_QUALIFIER : ProjectPreferences.PREFS_REGULAR_QUALIFIER;
if (create)
return new ProjectScope(project).getNode(qualifier).node(ResourcesPlugin.PREF_ENCODING);
Preferences node = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
try {
if (!node.nodeExists(project.getName()))
return null;
node = node.node(project.getName());
if (!node.nodeExists(qualifier))
return null;
node = node.node(qualifier);
if (!node.nodeExists(ResourcesPlugin.PREF_ENCODING))
return null;
return node.node(ResourcesPlugin.PREF_ENCODING);
} catch (BackingStoreException e) {
String message = Messages.resources_readingEncoding;
Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
}
return null;
}
private String internalGetCharsetFor(Preferences prefs, Preferences derivedPrefs, IPath resourcePath, boolean recurse) {
String charset = null;
if (prefs != null)
charset = prefs.get(getKeyFor(resourcePath), null);
if (charset == null && derivedPrefs != null)
charset = derivedPrefs.get(getKeyFor(resourcePath), null);
if (!recurse)
return charset;
while (charset == null && resourcePath.segmentCount() > 1) {
resourcePath = resourcePath.removeLastSegments(1);
if (prefs != null)
charset = prefs.get(getKeyFor(resourcePath), null);
if (charset == null && derivedPrefs != null)
charset = derivedPrefs.get(getKeyFor(resourcePath), null);
}
return charset == null ? ResourcesPlugin.getEncoding() : charset;
}
private boolean isDerivedEncodingStoredSeparately(IProject project) {
Preferences node = Platform.getPreferencesService().getRootNode().node(ProjectScope.SCOPE);
try {
if (!node.nodeExists(project.getName()))
return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
node = node.node(project.getName());
if (!node.nodeExists(ResourcesPlugin.PI_RESOURCES))
return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
node = node.node(ResourcesPlugin.PI_RESOURCES);
return node.getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS);
} catch (BackingStoreException e) {
String message = Messages.resources_readingEncoding;
Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
}
}
protected void mergeEncodingPreferences(IProject project) {
Preferences projectRegularPrefs = null;
Preferences projectDerivedPrefs = getPreferences(project, false, true, true);
if (projectDerivedPrefs == null)
return;
try {
boolean prefsChanged = false;
String[] affectedResources;
affectedResources = projectDerivedPrefs.keys();
for (String path : affectedResources) {
String value = projectDerivedPrefs.get(path, null);
projectDerivedPrefs.remove(path);
if (projectRegularPrefs == null)
projectRegularPrefs = getPreferences(project, true, false, false);
projectRegularPrefs.put(path, value);
prefsChanged = true;
}
if (prefsChanged) {
Map<IProject, Boolean> projectsToSave = new HashMap<>();
projectsToSave.put(project, Boolean.TRUE);
job.addChanges(projectsToSave);
}
} catch (BackingStoreException e) {
String message = Messages.resources_readingEncoding;
Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
}
}
public void projectPreferencesChanged(IProject project) {
charsetListener.charsetPreferencesChanged(project);
}
public void setCharsetFor(IPath resourcePath, String newCharset) throws CoreException {
if (resourcePath.segmentCount() == 0) {
IEclipsePreferences resourcesPreferences = InstanceScope.INSTANCE.getNode(ResourcesPlugin.PI_RESOURCES);
if (newCharset != null)
resourcesPreferences.put(ResourcesPlugin.PREF_ENCODING, newCharset);
else
resourcesPreferences.remove(ResourcesPlugin.PREF_ENCODING);
try {
resourcesPreferences.flush();
} catch (BackingStoreException e) {
IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
String message = Messages.resources_savingEncoding;
throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
}
return;
}
IResource resource = workspace.getRoot().findMember(resourcePath);
if (resource != null) {
try {
Preferences encodingSettings = getPreferences(resource.getProject(), true, resource.isDerived(IResource.CHECK_ANCESTORS));
if (newCharset == null || newCharset.trim().length() == 0)
encodingSettings.remove(getKeyFor(resourcePath));
else
encodingSettings.put(getKeyFor(resourcePath), newCharset);
flushPreferences(encodingSettings, true);
} catch (BackingStoreException e) {
IProject project = workspace.getRoot().getProject(resourcePath.segment(0));
String message = Messages.resources_savingEncoding;
throw new ResourceException(IResourceStatus.FAILED_SETTING_CHARSET, project.getFullPath(), message, e);
}
}
}
@Override
public void shutdown(IProgressMonitor monitor) {
workspace.removeResourceChangeListener(resourceChangeListener);
if (charsetListener != null)
charsetListener.shutdown();
}
protected void splitEncodingPreferences(IProject project) {
Preferences projectRegularPrefs = getPreferences(project, false, false, false);
Preferences projectDerivedPrefs = null;
if (projectRegularPrefs == null)
return;
try {
boolean prefsChanged = false;
String[] affectedResources;
affectedResources = projectRegularPrefs.keys();
for (String path : affectedResources) {
IResource resource = project.findMember(path);
if (resource != null) {
if (resource.isDerived(IResource.CHECK_ANCESTORS)) {
String value = projectRegularPrefs.get(path, null);
projectRegularPrefs.remove(path);
if (projectDerivedPrefs == null)
projectDerivedPrefs = getPreferences(project, true, true, true);
projectDerivedPrefs.put(path, value);
prefsChanged = true;
}
}
}
if (prefsChanged) {
Map<IProject, Boolean> projectsToSave = new HashMap<>();
projectsToSave.put(project, Boolean.TRUE);
job.addChanges(projectsToSave);
}
} catch (BackingStoreException e) {
String message = Messages.resources_readingEncoding;
Policy.log(new ResourceStatus(IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
}
}
@Override
public void startup(IProgressMonitor monitor) {
job = new CharsetManagerJob();
resourceChangeListener = new ResourceChangeListener();
workspace.addResourceChangeListener(resourceChangeListener, IResourceChangeEvent.POST_CHANGE);
charsetListener = new CharsetDeltaJob(workspace);
charsetListener.startup();
}
}