package org.eclipse.core.internal.resources;
import java.util.*;
import org.eclipse.core.internal.events.ILifecycleListener;
import org.eclipse.core.internal.events.LifecycleEvent;
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.osgi.util.NLS;
public class NatureManager implements ILifecycleListener, IManager {
private Map<String, IProjectNatureDescriptor> descriptors;
private final Map<Project, String[]> natureEnablements = new HashMap<>(20);
private Map<String, String> buildersToNatures;
private static final byte WHITE = 0;
private static final byte GREY = 1;
private static final byte BLACK = 2;
protected NatureManager() {
super();
}
protected String[] computeNatureEnablements(Project project) {
final ProjectDescription description = project.internalGetDescription();
if (description == null)
return new String[0];
String[] natureIds = description.getNatureIds();
int count = natureIds.length;
if (count == 0)
return natureIds;
HashSet<String> candidates = new HashSet<>(count * 2);
HashMap<String, ArrayList<String>> setsToNatures = new HashMap<>(count);
for (int i = 0; i < count; i++) {
String id = natureIds[i];
ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id);
if (desc == null)
continue;
if (!desc.hasCycle)
candidates.add(id);
String[] setIds = desc.getNatureSetIds();
for (String set : setIds) {
ArrayList<String> current = setsToNatures.get(set);
if (current == null) {
current = new ArrayList<>(5);
setsToNatures.put(set, current);
}
current.add(id);
}
}
for (ArrayList<String> setMembers : setsToNatures.values()) {
if (setMembers.size() > 1) {
candidates.removeAll(setMembers);
}
}
String[] orderedCandidates = candidates.toArray(new String[candidates.size()]);
orderedCandidates = sortNatureSet(orderedCandidates);
for (String id : orderedCandidates) {
IProjectNatureDescriptor desc = getNatureDescriptor(id);
String[] required = desc.getRequiredNatureIds();
for (String t : required) {
if (!candidates.contains(t)) {
candidates.remove(id);
break;
}
}
}
return candidates.toArray(new String[candidates.size()]);
}
public synchronized IProjectNatureDescriptor getNatureDescriptor(String natureId) {
lazyInitialize();
return descriptors.get(natureId);
}
public synchronized IProjectNatureDescriptor[] getNatureDescriptors() {
lazyInitialize();
Collection<IProjectNatureDescriptor> values = descriptors.values();
return values.toArray(new IProjectNatureDescriptor[values.size()]);
}
@Override
public void handleEvent(LifecycleEvent event) {
switch (event.kind) {
case LifecycleEvent.POST_PROJECT_CHANGE :
case LifecycleEvent.PRE_PROJECT_CLOSE :
case LifecycleEvent.PRE_PROJECT_DELETE :
case LifecycleEvent.PRE_PROJECT_MOVE :
case LifecycleEvent.PRE_PROJECT_OPEN :
flushEnablements((IProject) event.resource);
}
}
protected void configureNature(final Project project, final String natureID, final MultiStatus errors) {
ISafeRunnable code = new ISafeRunnable() {
@Override
public void run() throws Exception {
IProjectNature nature = createNature(project, natureID);
nature.configure();
ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true);
info.setNature(natureID, nature);
}
@Override
public void handleException(Throwable exception) {
if (exception instanceof CoreException)
errors.add(((CoreException) exception).getStatus());
else
errors.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_errorNature, natureID), exception));
}
};
if (Policy.DEBUG_NATURES) {
Policy.debug("Configuring nature: " + natureID + " on project: " + project.getName());
}
SafeRunner.run(code);
}
@SuppressWarnings({"unchecked"})
public void configureNatures(Project project, ProjectDescription oldDescription, ProjectDescription newDescription, MultiStatus status) {
HashSet<String> oldNatures = new HashSet<>(Arrays.asList(oldDescription.getNatureIds(false)));
HashSet<String> newNatures = new HashSet<>(Arrays.asList(newDescription.getNatureIds(false)));
if (oldNatures.equals(newNatures))
return;
HashSet<String> deletions = (HashSet<String>) oldNatures.clone();
HashSet<String> additions = (HashSet<String>) newNatures.clone();
additions.removeAll(oldNatures);
deletions.removeAll(newNatures);
IStatus result = validateAdditions(newNatures, additions, project);
if (!result.isOK()) {
status.merge(result);
return;
}
result = validateRemovals(newNatures, deletions);
if (!result.isOK()) {
status.merge(result);
return;
}
oldDescription.setNatureIds(newDescription.getNatureIds(true));
flushEnablements(project);
String[] ordered = null;
if (deletions.size() > 0) {
ordered = sortNatureSet(deletions.toArray(new String[deletions.size()]));
for (int i = ordered.length; --i >= 0;)
deconfigureNature(project, ordered[i], status);
}
if (additions.size() > 0) {
ordered = sortNatureSet(additions.toArray(new String[additions.size()]));
for (String element : ordered)
configureNature(project, element, status);
}
}
protected IProjectNature createNature(Project project, String natureID) throws CoreException {
IExtension extension = Platform.getExtensionRegistry().getExtension(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES, natureID);
if (extension == null) {
String message = NLS.bind(Messages.resources_natureExtension, natureID);
throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null);
}
IConfigurationElement[] configs = extension.getConfigurationElements();
if (configs.length < 1) {
String message = NLS.bind(Messages.resources_natureClass, natureID);
throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null);
}
IConfigurationElement config = null;
for (int i = 0; config == null && i < configs.length; i++)
if ("runtime".equalsIgnoreCase(configs[i].getName()))
config = configs[i];
if (config == null) {
String message = NLS.bind(Messages.resources_natureFormat, natureID);
throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, null);
}
try {
IProjectNature nature = (IProjectNature) config.createExecutableExtension("run");
nature.setProject(project);
return nature;
} catch (ClassCastException e) {
String message = NLS.bind(Messages.resources_natureImplement, natureID);
throw new ResourceException(Platform.PLUGIN_ERROR, project.getFullPath(), message, e);
}
}
protected void deconfigureNature(final Project project, final String natureID, final MultiStatus status) {
final ProjectInfo info = (ProjectInfo) project.getResourceInfo(false, true);
IProjectNature existingNature = info.getNature(natureID);
if (existingNature == null) {
try {
existingNature = createNature(project, natureID);
} catch (CoreException e) {
return;
}
}
final IProjectNature nature = existingNature;
ISafeRunnable code = new ISafeRunnable() {
@Override
public void run() throws Exception {
nature.deconfigure();
info.setNature(natureID, null);
}
@Override
public void handleException(Throwable exception) {
if (exception instanceof CoreException)
status.add(((CoreException) exception).getStatus());
else
status.add(new ResourceStatus(IResourceStatus.INTERNAL_ERROR, project.getFullPath(), NLS.bind(Messages.resources_natureDeconfig, natureID), exception));
}
};
if (Policy.DEBUG_NATURES) {
Policy.debug("Deconfiguring nature: " + natureID + " on project: " + project.getName());
}
SafeRunner.run(code);
}
private void detectCycles() {
Collection<IProjectNatureDescriptor> values = descriptors.values();
ProjectNatureDescriptor[] natures = values.toArray(new ProjectNatureDescriptor[values.size()]);
for (ProjectNatureDescriptor nature : natures)
if (nature.colour == WHITE)
hasCycles(nature);
}
protected IStatus failure(String reason) {
return new ResourceStatus(IResourceStatus.INVALID_NATURE_SET, reason);
}
public synchronized String findNatureForBuilder(String builderID) {
if (buildersToNatures == null) {
buildersToNatures = new HashMap<>(10);
IProjectNatureDescriptor[] descs = getNatureDescriptors();
for (IProjectNatureDescriptor desc : descs) {
String natureId = desc.getNatureId();
String[] builders = ((ProjectNatureDescriptor) desc).getBuilderIds();
for (String builder : builders) {
buildersToNatures.put(builder, natureId);
}
}
}
return buildersToNatures.get(builderID);
}
private synchronized void flushEnablements(IProject project) {
natureEnablements.remove(project);
}
protected synchronized String[] getEnabledNatures(Project project) {
String[] enabled = natureEnablements.get(project);
if (enabled != null)
return enabled;
enabled = computeNatureEnablements(project);
natureEnablements.put(project, enabled);
return enabled;
}
protected boolean hasCycles(ProjectNatureDescriptor desc) {
if (desc.colour == BLACK) {
return desc.hasCycle;
}
if (desc.colour == GREY) {
desc.hasCycle = true;
desc.colour = BLACK;
return true;
}
desc.colour = GREY;
String[] required = desc.getRequiredNatureIds();
for (String element : required) {
ProjectNatureDescriptor dependency = (ProjectNatureDescriptor) getNatureDescriptor(element);
if (dependency != null && hasCycles(dependency)) {
desc.hasCycle = true;
desc.colour = BLACK;
return true;
}
}
desc.hasCycle = false;
desc.colour = BLACK;
return false;
}
protected boolean hasLinks(IProject project) {
try {
IResource[] children = project.members();
for (IResource element : children)
if (element.isLinked())
return true;
} catch (CoreException e) {
Policy.log(e.getStatus());
}
return false;
}
protected String hasSetOverlap(IProjectNatureDescriptor one, IProjectNatureDescriptor two) {
if (one == null || two == null) {
return null;
}
String[] setsOne = one.getNatureSetIds();
String[] setsTwo = two.getNatureSetIds();
for (String element : setsOne) {
for (String element2 : setsTwo) {
if (element.equals(element2)) {
return element;
}
}
}
return null;
}
protected void insert(ArrayList<String> list, Set<String> seen, String id) {
if (seen.contains(id))
return;
seen.add(id);
IProjectNatureDescriptor desc = getNatureDescriptor(id);
if (desc != null) {
String[] prereqs = desc.getRequiredNatureIds();
for (String prereq : prereqs)
insert(list, seen, prereq);
}
list.add(id);
}
public boolean isNatureEnabled(Project project, String id) {
String[] enabled = getEnabledNatures(project);
for (String element : enabled) {
if (element.equals(id))
return true;
}
return false;
}
private void lazyInitialize() {
if (descriptors != null)
return;
IExtensionPoint point = Platform.getExtensionRegistry().getExtensionPoint(ResourcesPlugin.PI_RESOURCES, ResourcesPlugin.PT_NATURES);
IExtension[] extensions = point.getExtensions();
descriptors = new HashMap<>(extensions.length * 2 + 1);
for (IExtension extension : extensions) {
IProjectNatureDescriptor desc = null;
try {
desc = new ProjectNatureDescriptor(extension);
} catch (CoreException e) {
Policy.log(e.getStatus());
}
if (desc != null)
descriptors.put(desc.getNatureId(), desc);
}
detectCycles();
}
@Override
public void shutdown(IProgressMonitor monitor) {
}
public String[] sortNatureSet(String[] natureIds) {
int count = natureIds.length;
if (count == 0)
return natureIds;
ArrayList<String> result = new ArrayList<>(count);
HashSet<String> seen = new HashSet<>(count);
for (int i = 0; i < count; i++)
insert(result, seen, natureIds[i]);
seen.clear();
seen.addAll(Arrays.asList(natureIds));
for (Iterator<String> it = result.iterator(); it.hasNext();) {
Object id = it.next();
if (!seen.contains(id))
it.remove();
}
return result.toArray(new String[result.size()]);
}
@Override
public void startup(IProgressMonitor monitor) {
((Workspace) ResourcesPlugin.getWorkspace()).addLifecycleListener(this);
}
protected IStatus validateAdditions(HashSet<String> newNatures, HashSet<String> additions, IProject project) {
Boolean hasLinks = null;
for (String id : additions) {
IProjectNatureDescriptor desc = getNatureDescriptor(id);
if (desc == null) {
return failure(NLS.bind(Messages.natures_missingNature, id));
}
if (((ProjectNatureDescriptor) desc).hasCycle) {
return failure(NLS.bind(Messages.natures_hasCycle, id));
}
String[] required = desc.getRequiredNatureIds();
for (String r : required) {
if (!newNatures.contains(r)) {
return failure(NLS.bind(Messages.natures_missingPrerequisite, id, r));
}
}
for (String current : newNatures) {
if (!current.equals(id)) {
String overlap = hasSetOverlap(desc, getNatureDescriptor(current));
if (overlap != null) {
return failure(NLS.bind(Messages.natures_multipleSetMembers, overlap));
}
}
}
if (!desc.isLinkingAllowed()) {
if (hasLinks == null) {
hasLinks = hasLinks(project) ? Boolean.TRUE : Boolean.FALSE;
}
if (hasLinks.booleanValue())
return failure(NLS.bind(Messages.links_vetoNature, project.getName(), id));
}
}
return Status.OK_STATUS;
}
public IStatus validateLinkCreation(String[] natureIds) {
for (String natureId : natureIds) {
IProjectNatureDescriptor desc = getNatureDescriptor(natureId);
if (desc != null && !desc.isLinkingAllowed()) {
String msg = NLS.bind(Messages.links_natureVeto, desc.getLabel());
return new ResourceStatus(IResourceStatus.LINKING_NOT_ALLOWED, msg);
}
}
return Status.OK_STATUS;
}
protected IStatus validateRemovals(HashSet<String> newNatures, HashSet<String> deletions) {
for (String currentID : newNatures) {
IProjectNatureDescriptor desc = getNatureDescriptor(currentID);
if (desc != null) {
String[] required = desc.getRequiredNatureIds();
for (String element : required) {
if (deletions.contains(element)) {
return failure(NLS.bind(Messages.natures_invalidRemoval, element, currentID));
}
}
}
}
return Status.OK_STATUS;
}
public IStatus validateNatureSet(String[] natureIds) {
int count = natureIds.length;
if (count == 0)
return Status.OK_STATUS;
String msg = Messages.natures_invalidSet;
MultiStatus result = new MultiStatus(ResourcesPlugin.PI_RESOURCES, IResourceStatus.INVALID_NATURE_SET, msg, null);
HashSet<String> natures = new HashSet<>(count * 2);
HashSet<String> sets = new HashSet<>(count);
for (int i = 0; i < count; i++) {
String id = natureIds[i];
ProjectNatureDescriptor desc = (ProjectNatureDescriptor) getNatureDescriptor(id);
if (desc == null) {
result.add(failure(NLS.bind(Messages.natures_missingNature, id)));
continue;
}
if (desc.hasCycle)
result.add(failure(NLS.bind(Messages.natures_hasCycle, id)));
if (!natures.add(id))
result.add(failure(NLS.bind(Messages.natures_duplicateNature, id)));
String[] setIds = desc.getNatureSetIds();
for (String setId : setIds) {
if (!sets.add(setId)) {
result.add(failure(NLS.bind(Messages.natures_multipleSetMembers, setId)));
}
}
}
for (int i = 0; i < count; i++) {
IProjectNatureDescriptor desc = getNatureDescriptor(natureIds[i]);
if (desc == null)
continue;
String[] required = desc.getRequiredNatureIds();
for (String r : required) {
if (!natures.contains(r)) {
result.add(failure(NLS.bind(Messages.natures_missingPrerequisite, natureIds[i], r)));
}
}
}
return result.isOK() ? Status.OK_STATUS : (IStatus) result;
}
}