/*******************************************************************************
* Copyright (C) 2015, Obeo.
*
* 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
*******************************************************************************/
package org.eclipse.emf.compare.egit.internal.merge;
//CHECKSTYLE:OFF
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.mapping.IModelProviderDescriptor;
import org.eclipse.core.resources.mapping.ModelProvider;
import org.eclipse.core.resources.mapping.RemoteResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceMapping;
import org.eclipse.core.resources.mapping.ResourceMappingContext;
import org.eclipse.core.resources.mapping.ResourceTraversal;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.egit.core.Activator;
/**
* Provides a set of utility functions to traverse and discover the logical models corresponding to workspace
* resource. Take note that this class' model lookups will ignore a set of ModelProvider that only provide
* default information with no value regarding "merging" or "comparison".
*
* @see #ignoredModelDescriptors
* @author <a href="mailto:laurent.goubet@obeo.fr">Laurent Goubet</a>
* @author <a href="mailto:laurent.delaigue@obeo.fr">Laurent Delaigue</a>
*/
@SuppressWarnings("restriction")
public final class LogicalModels {
/**
* These model providers are active on most (for the first, "all") files. They provide a default merger
* that does not actually take care of model merging and are actually not defining a "larger model" : they
* only provide single-resource traversals. Since we are not interested in these defaults, we'll ignore
* them for all "model" lookup.
*/
private static final Set<String> ignoredModelDescriptors = new HashSet<String>(
Arrays.asList("org.eclipse.core.resources.modelProvider", //$NON-NLS-1$
"org.eclipse.jdt.ui.modelProvider", //$NON-NLS-1$
"org.eclipse.egit.ui.changeSetModel")); //$NON-NLS-1$
private Map<IResource, Set<IResource>> models = new HashMap<IResource, Set<IResource>>();
/**
* Iterate over the resources in the given set to discover and cache the logical model of each.
* <p>
* Resources that do not exist locally will be excluded from the lookup to avoid NPEs from the expression
* framework (model providers look for the resource's content type, which fails for remote resources). If
* these resources _are_ part of a larger model, it will be detected through the "other parts" of said
* model.
* </p>
* <p>
* Models provided by the {@link #ignoredModelDescriptors} will be ignored from this lookup.
* </p>
*
* @param resources
* @param remoteMappingContext
*/
public void build(Set<IResource> resources, RemoteResourceMappingContext remoteMappingContext) {
for (IResource supervisedResource : resources) {
if (supervisedResource instanceof IFile && !models.containsKey(supervisedResource)) {
final Set<IResource> model = discoverModel(supervisedResource, remoteMappingContext);
for (IResource resourceInModel : model) {
models.put(resourceInModel, model);
}
}
}
}
/**
* Returns the model which the given resource is a component of.
* <p>
* Note that this will always return <code>null</code> unless
* {@link #build(Set, RemoteResourceMappingContext)} has been called beforehand.
* </p>
*
* @param resource
* The resource which logical model we need.
* @return The logical model previously discovered for this resource through
* {@link #build(Set, RemoteResourceMappingContext)}.
*/
public Set<IResource> getModel(IResource resource) {
return models.get(resource);
}
/**
* This will check whether the given resource is a part of a logical model as described by the
* ModelProviders extension point.
*
* @param resource
* @param mappingContext
* @return the model which this resource is a part of.
*/
public static Set<IResource> discoverModel(IResource resource, ResourceMappingContext mappingContext) {
final Set<IResource> model = new LinkedHashSet<IResource>();
Set<IResource> newResources = new LinkedHashSet<IResource>();
newResources.add(resource);
do {
final Set<IResource> temp = newResources;
newResources = new LinkedHashSet<IResource>();
for (IResource res : temp) {
final Set<ResourceMapping> mappings = getResourceMappings(Collections.singleton(res),
mappingContext);
newResources.addAll(collectResources(mappings, mappingContext));
}
} while (model.addAll(newResources));
return model;
}
/**
* Try and find a model provider that matches the whole given logical model and that provides an adapter
* of the specific given type.
* <p>
* Models providers matching one of the {@link #ignoredModelDescriptors} will be ignored from this lookup.
* </p>
*
* @param model
* The logical model we need a merger for.
* @param adapterClass
* Kind of adapter we need.
* @return An adapter of the desired type for the provided logical model if any, <code>null</code> if
* none.
* @throws CoreException
* Thrown if we cannot query one or more of the model providers.
*/
public static <T> T findAdapter(Set<IResource> model, Class<T> adapterClass) throws CoreException {
if (model.isEmpty()) {
return null;
}
final IResource[] modelArray = model.toArray(new IResource[model.size()]);
final IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors();
// FIXME: Only care about EMFCompare model provider, dismiss all others?
for (int i = 0; i < descriptors.length; i++) {
final IModelProviderDescriptor descriptor = descriptors[i];
if (ignoredModelDescriptors.contains(descriptor.getId())) {
continue;
}
final IResource[] matchingResources = descriptor.getMatchingResources(modelArray);
if (matchingResources.length == modelArray.length) {
final ModelProvider provider = descriptor.getModelProvider();
T adapter = (T)provider.getAdapter(adapterClass);
if (adapter != null) {
// The first merger is used (arbitrary decision)
return adapter;
}
} else {
// This provider does not match the whole target model
}
}
return null;
}
/**
* @param model
* @param mappingContext
* @return all resource mappings related to the IResources of the given model.
*/
public static Set<ResourceMapping> getResourceMappings(Set<IResource> model,
ResourceMappingContext mappingContext) {
final Set<ResourceMapping> allMappings = new LinkedHashSet<ResourceMapping>();
final IResource[] modelArray = model.toArray(new IResource[model.size()]);
final IModelProviderDescriptor[] descriptors = ModelProvider.getModelProviderDescriptors();
for (IModelProviderDescriptor descriptor : descriptors) {
if (ignoredModelDescriptors.contains(descriptor.getId())) {
continue;
}
try {
final IResource[] matchingResources = descriptor.getMatchingResources(modelArray);
if (matchingResources.length > 0) {
final ModelProvider modelProvider = descriptor.getModelProvider();
final ResourceMapping[] modelMappings = modelProvider.getMappings(modelArray,
mappingContext, new NullProgressMonitor());
allMappings.addAll(Arrays.asList(modelMappings));
}
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
}
}
return allMappings;
}
private static Set<IResource> collectResources(Set<ResourceMapping> mappings,
ResourceMappingContext mappingContext) {
final Set<IResource> resources = new LinkedHashSet<IResource>();
for (ResourceMapping mapping : mappings) {
try {
final ResourceTraversal[] traversals = mapping.getTraversals(mappingContext,
new NullProgressMonitor());
for (ResourceTraversal traversal : traversals) {
resources.addAll(Arrays.asList(traversal.getResources()));
}
} catch (CoreException e) {
Activator.logError(e.getMessage(), e);
}
}
return resources;
}
}
// CHECKSTYLE:ON