protected static String getLineDelimiterPreference(IFile file) {
   IScopeContext[] scopeContext;
   if (file != null && file.getProject() != null) {
     // project preference
     scopeContext = new IScopeContext[] {new ProjectScope(file.getProject())};
     String lineDelimiter =
         Platform.getPreferencesService()
             .getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, scopeContext);
     if (lineDelimiter != null) return lineDelimiter;
   }
   // workspace preference
   scopeContext = new IScopeContext[] {InstanceScope.INSTANCE};
   return Platform.getPreferencesService()
       .getString(Platform.PI_RUNTIME, Platform.PREF_LINE_SEPARATOR, null, scopeContext);
 }
 private boolean isDerivedEncodingStoredSeparately(IProject project) {
   // 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;
     //			return node.nodeExists(path) ?
     // node.node(path).getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, false) : false;
     // for now, take the long way
     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) {
     // nodeExists failed
     String message = Messages.resources_readingEncoding;
     Policy.log(
         new ResourceStatus(
             IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
     return ResourcesPlugin.DEFAULT_PREF_SEPARATE_DERIVED_ENCODINGS;
   }
 }
Beispiel #3
0
 @Override
 public String getCharsetFor(Reader contents) throws CoreException {
   String charset;
   ResourceInfo info = getResourceInfo(false, false);
   int flags = getFlags(info);
   if (exists(flags, true))
     // the file exists, look for user setting
     if ((charset = workspace.getCharsetManager().getCharsetFor(getFullPath(), false)) != null)
       // if there is a file-specific user setting, use it
       return charset;
   // tries to obtain a description from the contents provided
   IContentDescription description;
   try {
     // TODO need to take project specific settings into account
     IContentTypeManager contentTypeManager = Platform.getContentTypeManager();
     description =
         contentTypeManager.getDescriptionFor(
             contents, getName(), new QualifiedName[] {IContentDescription.CHARSET});
   } catch (IOException e) {
     String message = NLS.bind(Messages.resources_errorContentDescription, getFullPath());
     throw new ResourceException(
         IResourceStatus.FAILED_DESCRIBING_CONTENTS, getFullPath(), message, e);
   }
   if (description != null)
     if ((charset = description.getCharset()) != null)
       // the description contained charset info, we are done
       return charset;
   // could not find out the encoding based on the contents... default to parent's
   return workspace.getCharsetManager().getCharsetFor(getFullPath().removeLastSegments(1), true);
 }
  /**
   * Tries to delete an open project containing an irremovable file. Works only for Linux with
   * natives.
   */
  public void testDeleteOpenProjectLinux() {
    if (!(Platform.getOS().equals(Platform.OS_LINUX) && isReadOnlySupported())) return;

    IProject project = null;
    File projectRoot = null;
    IFolder folder = null;
    try {
      IWorkspace workspace = getWorkspace();
      project = workspace.getRoot().getProject(getUniqueString());
      folder = project.getFolder("a_folder");
      IFile file1 = folder.getFile("file1.txt");
      IFile file2 = project.getFile("file2.txt");

      ensureExistsInWorkspace(new IResource[] {file1, file2}, true);
      projectRoot = project.getLocation().toFile();

      // marks folder as read-only so its files cannot be deleted on Linux
      setReadOnly(folder, true);

      IFile projectFile = project.getFile(".project");
      assertTrue("1.2", projectFile.exists());
      assertTrue("1.3", projectFile.isSynchronized(IResource.DEPTH_INFINITE));

      try {
        project.delete(IResource.FORCE, getMonitor());
        fail("2.0 - should have failed");
      } catch (CoreException ce) {
        // success - a file couldn't be removed
      }
      assertTrue("2.1", project.exists());
      assertTrue("2.2", file1.exists());
      assertTrue("2.3", !file2.exists());
      assertTrue("2.5", folder.exists());
      assertTrue("2.6", projectFile.exists());
      assertTrue("2.7", project.isSynchronized(IResource.DEPTH_INFINITE));

      setReadOnly(folder, false);

      assertTrue("3.5", project.isSynchronized(IResource.DEPTH_INFINITE));
      try {
        project.delete(IResource.FORCE, getMonitor());
      } catch (CoreException ce) {
        ce.printStackTrace();
        fail("4.0", ce);
      }

      assertTrue("5.1", !project.exists());
      assertTrue("5.2", !file1.exists());
      assertTrue("5.3", file1.isSynchronized(IResource.DEPTH_INFINITE));
      assertTrue("5.4", project.isSynchronized(IResource.DEPTH_INFINITE));

      assertTrue("6.0", !projectRoot.exists());
    } finally {
      if (folder != null && folder.exists()) setReadOnly(folder, false);
      if (projectRoot != null) ensureDoesNotExistInFileSystem(projectRoot);
    }
  }
 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)
     // create all nodes down to the one we are interested in
     return new ProjectScope(project).getNode(qualifier).node(ResourcesPlugin.PREF_ENCODING);
   // 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(qualifier)) return null;
     node = node.node(qualifier);
     if (!node.nodeExists(ResourcesPlugin.PREF_ENCODING)) return null;
     return node.node(ResourcesPlugin.PREF_ENCODING);
   } catch (BackingStoreException e) {
     // nodeExists failed
     String message = Messages.resources_readingEncoding;
     Policy.log(
         new ResourceStatus(
             IResourceStatus.FAILED_GETTING_CHARSET, project.getFullPath(), message, e));
   }
   return null;
 }
Beispiel #6
0
 static {
   String value = Platform.getDebugOption("org.eclipse.php.core/debug"); // $NON-NLS-1$
   isDebugMode = value != null && value.equalsIgnoreCase("true"); // $NON-NLS-1$
 }
/**
 * 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<Map.Entry<IProject, Boolean>> asyncChanges = new ArrayList<>();

    public CharsetManagerJob() {
      super(Messages.resources_charsetUpdating);
      setSystem(true);
      setPriority(Job.INTERACTIVE);
    }

    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) {
            // 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;
            IProject project = next.getKey();
            try {
              if (project.isAccessible()) {
                boolean shouldDisableCharsetDeltaJob = next.getValue().booleanValue();
                // flush preferences for non-derived resources
                flushPreferences(
                    getPreferences(project, false, false, true), shouldDisableCharsetDeltaJob);
                // flush preferences for derived resources
                flushPreferences(
                    getPreferences(project, false, true, true), shouldDisableCharsetDeltaJob);
              }
            } catch (BackingStoreException e) {
              // we got an error saving
              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, Policy.subMonitorFor(monitor, Policy.endOpWork));
        }
      } 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) {
        // if derived changed, move encoding to correct preferences
        IPath parentPath = parent.getResource().getProjectRelativePath();
        for (int i = 0; i < affectedResources.length; i++) {
          IPath affectedPath = new Path(affectedResources[i]);
          // if parentPath is an ancestor of affectedPath
          if (parentPath.isPrefixOf(affectedPath)) {
            IResource member = currentProject.findMember(affectedPath);
            if (member != null) {
              Preferences targetPrefs =
                  getPreferences(currentProject, true, member.isDerived(IResource.CHECK_ANCESTORS));
              // if new preferences are different than current
              if (!projectPrefs.absolutePath().equals(targetPrefs.absolutePath())) {
                // remove encoding from old preferences and save in correct preferences
                String currentValue = projectPrefs.get(affectedResources[i], null);
                projectPrefs.remove(affectedResources[i]);
                targetPrefs.put(affectedResources[i], currentValue);
                resourceChanges = true;
              }
            }
          }
        }
      }

      IResourceDelta[] children = parent.getAffectedChildren();
      for (int i = 0; i < children.length; i++) {
        resourceChanges =
            moveSettingsIfDerivedChanged(
                    children[i], currentProject, projectPrefs, affectedResources)
                || resourceChanges;
      }
      return resourceChanges;
    }

    private void processEntryChanges(
        IResourceDelta projectDelta, Map<IProject, Boolean> projectsToSave) {
      // check each resource with user-set encoding to see if it has
      // been moved/deleted or if derived state has been changed
      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 {
        // no regular preferences for this project
        if (projectRegularPrefs == null) affectedResourcesMap.put(Boolean.FALSE, new String[0]);
        else affectedResourcesMap.put(Boolean.FALSE, projectRegularPrefs.keys());
        // no derived preferences for this project
        if (projectDerivedPrefs == null) affectedResourcesMap.put(Boolean.TRUE, new String[0]);
        else affectedResourcesMap.put(Boolean.TRUE, projectDerivedPrefs.keys());
      } catch (BackingStoreException e) {
        // problems with the project scope... we will 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 (Iterator<Boolean> it = affectedResourcesMap.keySet().iterator(); it.hasNext(); ) {
        Boolean isDerived = it.next();
        String[] affectedResources = affectedResourcesMap.get(isDerived);
        Preferences projectPrefs =
            isDerived.booleanValue() ? projectDerivedPrefs : projectRegularPrefs;
        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) {
            boolean shouldDisableCharsetDeltaJobForCurrentProject = false;
            // 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) {
              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))
                  // if the file was moved inside the same project disable charset listener
                  shouldDisableCharsetDeltaJobForCurrentProject = true;
                else projectsToSave.put(targetProject, Boolean.FALSE);
              }
            }
            projectsToSave.put(
                currentProject, Boolean.valueOf(shouldDisableCharsetDeltaJobForCurrentProject));
          }
        }
        if (moveSettingsIfDerivedChanged(
            projectDelta, currentProject, projectPrefs, affectedResources)) {
          // if settings were moved between preferences files disable charset listener so we don't
          // react to changes made by ourselves
          projectsToSave.put(currentProject, Boolean.TRUE);
        }
      }
    }

    /**
     * For any change to the encoding file or any resource with encoding set, just discard the cache
     * for the corresponding project.
     */
    @Override
    public void resourceChanged(IResourceChangeEvent event) {
      IResourceDelta delta = event.getDelta();
      if (delta == null) return;
      IResourceDelta[] projectDeltas = delta.getAffectedChildren();
      // process each project in the delta
      Map<IProject, Boolean> projectsToSave = new HashMap<>();
      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);
    }
  }

  private static final String PROJECT_KEY = "<project>"; // $NON-NLS-1$
  private CharsetDeltaJob charsetListener;
  CharsetManagerJob job;
  private IResourceChangeListener resourceChangeListener;
  protected final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); // $NON-NLS-1$
  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);
      }
    }
  }

  /**
   * 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 prefs = getPreferences(project, false, false);
    Preferences derivedPrefs = getPreferences(project, false, true);

    if (prefs == null && derivedPrefs == 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(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)
      // create all nodes down to the one we are interested in
      return new ProjectScope(project).getNode(qualifier).node(ResourcesPlugin.PREF_ENCODING);
    // 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(qualifier)) return null;
      node = node.node(qualifier);
      if (!node.nodeExists(ResourcesPlugin.PREF_ENCODING)) return null;
      return node.node(ResourcesPlugin.PREF_ENCODING);
    } 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(
      Preferences prefs, Preferences derivedPrefs, IPath resourcePath, boolean recurse) {
    String charset = null;

    // try to find the encoding in regular and then derived preferences
    if (prefs != null) charset = prefs.get(getKeyFor(resourcePath), null);
    // derivedPrefs may be not null, only if #isDerivedEncodingStoredSeparately returns true
    // so the explicit check against #isDerivedEncodingStoredSeparately is not required
    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);
      // try to find the encoding in regular and then derived preferences
      if (prefs != null) charset = prefs.get(getKeyFor(resourcePath), null);
      if (charset == null && derivedPrefs != null)
        charset = derivedPrefs.get(getKeyFor(resourcePath), null);
    }

    // ensure we default to the workspace encoding if none is found
    return charset == null ? ResourcesPlugin.getEncoding() : charset;
  }

  private boolean isDerivedEncodingStoredSeparately(IProject project) {
    // 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;
      //			return node.nodeExists(path) ?
      // node.node(path).getBoolean(ResourcesPlugin.PREF_SEPARATE_DERIVED_ENCODINGS, false) : false;
      // for now, take the long way
      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) {
      // nodeExists failed
      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 (int i = 0; i < affectedResources.length; i++) {
        String path = affectedResources[i];
        String value = projectDerivedPrefs.get(path, null);
        projectDerivedPrefs.remove(path);
        // lazy creation of non-derived preferences
        if (projectRegularPrefs == null)
          projectRegularPrefs = getPreferences(project, true, false, false);
        projectRegularPrefs.put(path, value);
        prefsChanged = true;
      }
      if (prefsChanged) {
        Map<IProject, Boolean> projectsToSave = new HashMap<>();
        // this is internal change so do not notify charset delta job
        projectsToSave.put(project, Boolean.TRUE);
        job.addChanges(projectsToSave);
      }
    } catch (BackingStoreException e) {
      // problems with the project scope... we will miss the changes (but will log)
      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 {
    // for the workspace root we just set a preference in the instance scope
    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;
    }
    // for all other cases, we set a property in the corresponding project
    IResource resource = workspace.getRoot().findMember(resourcePath);
    if (resource != null) {
      try {
        // disable the listener so we don't react to changes made by ourselves
        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 (int i = 0; i < affectedResources.length; i++) {
        String path = affectedResources[i];
        IResource resource = project.findMember(path);
        if (resource != null) {
          if (resource.isDerived(IResource.CHECK_ANCESTORS)) {
            String value = projectRegularPrefs.get(path, null);
            projectRegularPrefs.remove(path);
            // lazy creation of derived preferences
            if (projectDerivedPrefs == null)
              projectDerivedPrefs = getPreferences(project, true, true, true);
            projectDerivedPrefs.put(path, value);
            prefsChanged = true;
          }
        }
      }
      if (prefsChanged) {
        Map<IProject, Boolean> projectsToSave = new HashMap<>();
        // this is internal change so do not notify charset delta job
        projectsToSave.put(project, Boolean.TRUE);
        job.addChanges(projectsToSave);
      }
    } catch (BackingStoreException e) {
      // problems with the project scope... we will miss the changes (but will log)
      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();
  }
}