| /******************************************************************************* |
| * Copyright (c) 2004, 2015 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 |
| * James Blackburn (Broadcom Corp.) - ongoing development |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 473427 |
| *******************************************************************************/ |
| package org.eclipse.core.internal.resources; |
| |
| import org.eclipse.core.internal.utils.*; |
| import org.eclipse.core.internal.watson.*; |
| import org.eclipse.core.resources.*; |
| import org.eclipse.core.runtime.*; |
| import org.eclipse.core.runtime.content.IContentTypeManager; |
| import org.eclipse.core.runtime.content.IContentTypeManager.ContentTypeChangeEvent; |
| import org.eclipse.core.runtime.jobs.Job; |
| import org.osgi.framework.Bundle; |
| |
| /** |
| * Detects changes to content types/project preferences and |
| * broadcasts any corresponding encoding changes as resource deltas. |
| */ |
| |
| public class CharsetDeltaJob extends Job implements IContentTypeManager.IContentTypeChangeListener { |
| |
| // this is copied in the runtime tests - if changed here, has to be changed there too |
| public final static String FAMILY_CHARSET_DELTA = ResourcesPlugin.PI_RESOURCES + "charsetJobFamily"; //$NON-NLS-1$ |
| |
| interface ICharsetListenerFilter { |
| |
| /** |
| * Returns the path for the node in the tree we are interested in. Returns <code>null</code> |
| * if the visitor no longer wants to visit anything. |
| */ |
| IPath getRoot(); |
| |
| /** |
| * Returns whether the corresponding resource is affected by this change. |
| */ |
| boolean isAffected(ResourceInfo info, IPathRequestor requestor); |
| } |
| |
| private ThreadLocal<Boolean> disabled = new ThreadLocal<>(); |
| |
| private final Bundle systemBundle = Platform.getBundle("org.eclipse.osgi"); //$NON-NLS-1$ |
| private Queue<ICharsetListenerFilter> work = new Queue<>(); |
| |
| Workspace workspace; |
| |
| private static final int CHARSET_DELTA_DELAY = 500; |
| |
| public CharsetDeltaJob(Workspace workspace) { |
| super(Messages.resources_charsetBroadcasting); |
| this.workspace = workspace; |
| } |
| |
| private void addToQueue(ICharsetListenerFilter filter) { |
| synchronized (work) { |
| work.add(filter); |
| } |
| schedule(CHARSET_DELTA_DELAY); |
| } |
| |
| @Override |
| public boolean belongsTo(Object family) { |
| return FAMILY_CHARSET_DELTA.equals(family); |
| } |
| |
| public void charsetPreferencesChanged(final IProject project) { |
| // avoid reacting to changes made by ourselves |
| if (isDisabled()) |
| return; |
| ResourceInfo projectInfo = ((Project) project).getResourceInfo(false, false); |
| //nothing to do if project has already been deleted |
| if (projectInfo == null) |
| return; |
| final long projectId = projectInfo.getNodeId(); |
| // ensure all resources under the affected project are |
| // reported as having encoding changes |
| ICharsetListenerFilter filter = new ICharsetListenerFilter() { |
| @Override |
| public IPath getRoot() { |
| //make sure it is still the same project - it could have been deleted and recreated |
| ResourceInfo currentInfo = ((Project) project).getResourceInfo(false, false); |
| if (currentInfo == null) |
| return null; |
| long currentId = currentInfo.getNodeId(); |
| if (currentId != projectId) |
| return null; |
| // visit the project subtree |
| return project.getFullPath(); |
| } |
| |
| @Override |
| public boolean isAffected(ResourceInfo info, IPathRequestor requestor) { |
| // for now, mark all resources in the project as potential encoding resource changes |
| return true; |
| } |
| }; |
| addToQueue(filter); |
| } |
| |
| @Override |
| public void contentTypeChanged(final ContentTypeChangeEvent event) { |
| // check all files that may be affected by this change (taking |
| // only the current content type state into account |
| // dispatch a job to generate the deltas |
| ICharsetListenerFilter filter = new ICharsetListenerFilter() { |
| |
| @Override |
| public IPath getRoot() { |
| // visit all resources in the workspace |
| return Path.ROOT; |
| } |
| |
| @Override |
| public boolean isAffected(ResourceInfo info, IPathRequestor requestor) { |
| if (info.getType() != IResource.FILE) |
| return false; |
| return event.getContentType().isAssociatedWith(requestor.requestName()); |
| } |
| }; |
| addToQueue(filter); |
| } |
| |
| private boolean isDisabled() { |
| return disabled.get() != null; |
| } |
| |
| private void processNextEvent(final ICharsetListenerFilter filter, IProgressMonitor monitor) throws CoreException { |
| IElementContentVisitor visitor = new IElementContentVisitor() { |
| @Override |
| public boolean visitElement(ElementTree tree, IPathRequestor requestor, Object elementContents) { |
| ResourceInfo info = (ResourceInfo) elementContents; |
| if (!filter.isAffected(info, requestor)) |
| return true; |
| info = workspace.getResourceInfo(requestor.requestPath(), false, true); |
| if (info == null) |
| return false; |
| info.incrementCharsetGenerationCount(); |
| return true; |
| } |
| }; |
| try { |
| IPath root = filter.getRoot(); |
| if (root != null) |
| new ElementTreeIterator(workspace.getElementTree(), root).iterate(visitor); |
| } catch (WrappedRuntimeException e) { |
| throw (CoreException) e.getTargetException(); |
| } |
| if (monitor.isCanceled()) |
| throw new OperationCanceledException(); |
| } |
| |
| private ICharsetListenerFilter removeFromQueue() { |
| synchronized (work) { |
| return work.remove(); |
| } |
| } |
| |
| @Override |
| public IStatus run(IProgressMonitor monitor) { |
| monitor = Policy.monitorFor(monitor); |
| try { |
| String message = Messages.resources_charsetBroadcasting; |
| monitor.beginTask(message, Policy.totalWork); |
| try { |
| workspace.prepareOperation(null, monitor); |
| workspace.beginOperation(true); |
| ICharsetListenerFilter next; |
| //if the system is shutting down, don't broadcast |
| while (systemBundle.getState() != Bundle.STOPPING && (next = removeFromQueue()) != null) |
| processNextEvent(next, monitor); |
| } catch (OperationCanceledException e) { |
| workspace.getWorkManager().operationCanceled(); |
| return Status.CANCEL_STATUS; |
| } finally { |
| workspace.endOperation(null, true); |
| } |
| monitor.worked(Policy.opWork); |
| } catch (CoreException sig) { |
| return sig.getStatus(); |
| } finally { |
| monitor.done(); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Turns off reaction to changes in the preference file. |
| */ |
| public void setDisabled(boolean disabled) { |
| // using a thread local because this can be called by multiple threads concurrently |
| this.disabled.set(disabled ? Boolean.TRUE : null); |
| } |
| |
| public void shutdown() { |
| IContentTypeManager contentTypeManager = Platform.getContentTypeManager(); |
| //if the service is already gone there is nothing to do |
| if (contentTypeManager != null) |
| contentTypeManager.removeContentTypeChangeListener(this); |
| } |
| |
| public void startup() { |
| Platform.getContentTypeManager().addContentTypeChangeListener(this); |
| } |
| } |