/*******************************************************************************
* Copyright (c) 2008, 2009 Composent, Inc. 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:
* Composent, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.ecf.internal.sync.resources.core;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Plugin;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.preferences.DefaultScope;
import org.eclipse.core.runtime.preferences.InstanceScope;
import org.eclipse.ecf.core.identity.ID;
import org.eclipse.ecf.core.util.ECFException;
import org.eclipse.ecf.sync.IModelChange;
import org.eclipse.ecf.sync.IModelChangeMessage;
import org.eclipse.ecf.sync.resources.core.preferences.PreferenceConstants;
import org.osgi.framework.BundleContext;
import org.osgi.service.prefs.Preferences;
public class SyncResourcesCore extends Plugin implements
IResourceChangeListener, IResourceDeltaVisitor {
public static final String PLUGIN_ID = "org.eclipse.ecf.sync.resources.core"; //$NON-NLS-1$
private static final int LIMIT = 25;
private static final Hashtable channels = new Hashtable();
private static LinkedList enqueuedChanges = new LinkedList();
private static SyncResourcesCore instance;
private Map resourceChanges = new HashMap();
public static ResourcesShare getResourcesShare(ID containerID) {
return (ResourcesShare) channels.get(containerID);
}
public static void addResourcesShare(ID containerID, ResourcesShare share) {
channels.put(containerID, share);
}
public static ResourcesShare removeResourcesShare(ID containerID) {
return (ResourcesShare) channels.remove(containerID);
}
public static Collection getResourceShares() {
return Collections.unmodifiableCollection(channels.values());
}
/**
* Checks and returns whether the specified project is currently being
* shared.
*/
public static boolean isSharing(String projectName) {
for (Iterator it = channels.values().iterator(); it.hasNext();) {
ResourcesShare share = (ResourcesShare) it.next();
if (share.isSharing(projectName)) {
return true;
}
}
return false;
}
private static boolean locked = false;
/**
* Requests that all resource changes be ignored. That is, they should not
* be monitored for distribution to remote peers.
*/
public static void lock() {
locked = true;
}
/**
* Unlocks by requesting that that all resource changes be monitored for
* distribution to remote peers.
*/
public static void unlock() {
locked = false;
}
public boolean visit(IResourceDelta delta) throws CoreException {
if (locked) {
return false;
}
IResource resource = delta.getResource();
int type = resource.getType();
if (type == IResource.ROOT) {
return true;
}
String projectName = resource.getProject().getName();
boolean isSharing = isSharing(projectName);
if (isSharing) {
if (type == IResource.PROJECT) {
return true;
}
} else {
// this project is not being shared so we don't care about its
// changes, return false
return false;
}
// we are only interested in non-derived resources
if (!resource.isDerived() && checkDelta(delta)) {
for (Iterator it = channels.values().iterator(); it.hasNext();) {
ResourcesShare share = (ResourcesShare) it.next();
if (share.isSharing(projectName)) {
IModelChange change = ResourceChangeMessage
.createResourceChange(resource, delta.getKind());
if (change != null) {
List changes = (List) resourceChanges.get(share);
if (changes == null) {
changes = new ArrayList();
resourceChanges.put(share, changes);
}
changes.add(change);
}
}
}
}
return type == IResource.FOLDER;
}
private static boolean checkDelta(IResourceDelta delta) {
return checkFlags(delta.getFlags()) || checkKind(delta.getKind());
}
private static boolean checkKind(int kind) {
return kind == IResourceDelta.ADDED || kind == IResourceDelta.REMOVED;
}
/**
* Checks the flags of a how a resource has been modified and returns
* whether this change should be propagated to remote peers.
*
* @param flags
* the detail flags of a resource delta
* @return <code>true</code> if the delta should be propagated,
* <code>false</code> otherwise
*/
private static boolean checkFlags(int flags) {
// we are only interested in content change, marker changes also fire
// resource change events but we don't care about those, from
// preliminary investigations, monitoring marker changes will cause
// infinite loops
return (flags & IResourceDelta.CONTENT) != 0;
}
public void resourceChanged(IResourceChangeEvent event) {
try {
event.getDelta().accept(this);
} catch (CoreException e) {
// ignored, this isn't possible as we never throw one
} finally {
try {
// we're done, at least, "partially", distribute changes
distributeChanges();
} finally {
// clear the cached changes
resourceChanges.clear();
}
}
}
private void distributeChanges() {
for (Iterator it = resourceChanges.entrySet().iterator(); it.hasNext();) {
Entry entry = (Entry) it.next();
ResourcesShare share = (ResourcesShare) entry.getKey();
List changes = (List) entry.getValue();
List messages = new ArrayList();
for (int i = 0; i < changes.size(); i++) {
ResourceChangeMessage change = (ResourceChangeMessage) changes
.get(i);
switch (change.getKind()) {
case IResourceDelta.ADDED:
if (getInt(PreferenceConstants.LOCAL_RESOURCE_ADDITION) == PreferenceConstants.IGNORE_VALUE) {
change.setIgnored(true);
continue;
}
break;
case IResourceDelta.CHANGED:
if (getInt(PreferenceConstants.LOCAL_RESOURCE_CHANGE) == PreferenceConstants.IGNORE_VALUE) {
change.setIgnored(true);
continue;
}
break;
case IResourceDelta.REMOVED:
if (getInt(PreferenceConstants.LOCAL_RESOURCE_DELETION) == PreferenceConstants.IGNORE_VALUE) {
change.setIgnored(true);
continue;
}
break;
}
IModelChangeMessage[] changeMessages = ResourcesSynchronizationStrategy
.getInstance().registerLocalChange(change);
messages.addAll(Arrays.asList(changeMessages));
}
try {
if (!messages.isEmpty()) {
IModelChangeMessage[] messagesArray = (IModelChangeMessage[]) messages
.toArray(new IModelChangeMessage[messages.size()]);
BatchModelChange batchChange = new BatchModelChange(
messagesArray);
share.send(Message.serialize(batchChange));
add(batchChange);
}
} catch (ECFException e) {
getDefault().getLog().log(
new Status(IStatus.ERROR, PLUGIN_ID,
"Could not send resource change message", e)); //$NON-NLS-1$
}
}
}
private static IView viewInstance;
public static synchronized void setView(IView resourcesView) {
viewInstance = resourcesView;
if (resourcesView != null) {
resourcesView.setInput(enqueuedChanges);
}
}
public static synchronized void add(Object object) {
if (enqueuedChanges.size() == LIMIT) {
remove();
}
enqueuedChanges.addFirst(object);
if (viewInstance != null) {
viewInstance.add(object);
}
}
private static synchronized void remove() {
Object object = enqueuedChanges.removeLast();
if (viewInstance != null) {
viewInstance.remove(object);
}
}
private static Preferences preferences;
private static Preferences defaultPreferences;
public SyncResourcesCore() {
instance = this;
}
void attachListener() {
ResourcesPlugin.getWorkspace().addResourceChangeListener(this,
IResourceChangeEvent.POST_CHANGE);
}
void detachListener() {
ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
}
/*
* (non-Javadoc)
*
* @see
* org.osgi.framework.BundleActivator#start(org.osgi.framework.BundleContext
* )
*/
public void start(BundleContext ctxt) throws Exception {
super.start(ctxt);
attachListener();
preferences = new InstanceScope().getNode(SyncResourcesCore.PLUGIN_ID);
defaultPreferences = new DefaultScope()
.getNode(SyncResourcesCore.PLUGIN_ID);
}
/*
* (non-Javadoc)
*
* @see
* org.osgi.framework.BundleActivator#stop(org.osgi.framework.BundleContext)
*/
public void stop(BundleContext context) throws Exception {
instance = null;
detachListener();
super.stop(context);
}
public static SyncResourcesCore getDefault() {
return instance;
}
public static int getInt(String key) {
return preferences.getInt(key, defaultPreferences.getInt(key,
PreferenceConstants.COMMIT_VALUE));
}
}