blob: 3579f35c4a5970e586e273d96a89532f4e9cc011 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2017 xored software, Inc.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0.
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* xored software, Inc. - initial API and Implementation (Alex Panchenko)
*******************************************************************************/
package org.eclipse.dltk.internal.core.builder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.IScriptProject;
import org.eclipse.dltk.core.builder.IBuildParticipant;
import org.eclipse.dltk.core.builder.IBuildParticipantExtension4;
import org.eclipse.dltk.core.builder.IBuildParticipantFactory;
import org.eclipse.dltk.core.builder.IBuildParticipantFilter;
import org.eclipse.dltk.core.builder.IBuildParticipantFilterFactory;
import org.eclipse.dltk.internal.core.builder.BuildParticipantManager.FactoryValue;
import org.eclipse.dltk.utils.NatureExtensionManager;
import org.eclipse.osgi.util.NLS;
public class BuildParticipantManager
extends NatureExtensionManager<FactoryValue> {
private static final String EXT_POINT = DLTKCore.PLUGIN_ID
+ ".buildParticipant"; //$NON-NLS-1$
public static class FactoryValue<T> {
final T factory;
public FactoryValue(T factory) {
this.factory = factory;
}
}
public static class BuildParticipantFactoryValue
extends FactoryValue<IBuildParticipantFactory> {
final String id;
final String name;
public final Set<String> requirements = new HashSet<>();
/**
* @param factory
*/
public BuildParticipantFactoryValue(IBuildParticipantFactory factory,
String id, String name) {
super(factory);
this.id = id != null ? id : factory.getClass().getName();
this.name = name;
}
}
public static class FilterFactoryValue
extends FactoryValue<IBuildParticipantFilterFactory> {
public FilterFactoryValue(IBuildParticipantFilterFactory factory) {
super(factory);
}
}
private BuildParticipantManager() {
super(EXT_POINT, FactoryValue.class);
}
private static final String REQUIRES = "requires"; //$NON-NLS-1$
private static final String REQUIRES_ID = "id"; //$NON-NLS-1$
private static final String PARTICIPANT = "buildParticipant";
private static final String FILTER = "filter";
private static final String ATTR_ID = "id"; //$NON-NLS-1$
private static final String ATTR_NAME = "name"; //$NON-NLS-1$
@Override
protected Object createInstanceByDescriptor(Object input)
throws CoreException {
final IConfigurationElement element = (IConfigurationElement) input;
if (PARTICIPANT.equals(element.getName())) {
final Object factory = element.createExecutableExtension(classAttr);
if (!(factory instanceof IBuildParticipantFactory)) {
DLTKCore.warn(NLS.bind(
"{0} contributed by {1} must implement {2}",
new Object[] { element.getName(),
element.getContributor(),
IBuildParticipantFactory.class.getName() }));
return null;
}
final BuildParticipantFactoryValue factoryValue = new BuildParticipantFactoryValue(
(IBuildParticipantFactory) factory,
element.getAttribute(ATTR_ID),
element.getAttribute(ATTR_NAME));
final IConfigurationElement[] requires = element
.getChildren(REQUIRES);
for (int i = 0; i < requires.length; ++i) {
final String id = requires[i].getAttribute(REQUIRES_ID);
if (id != null) {
factoryValue.requirements.add(id);
}
}
return factoryValue;
} else if (FILTER.equals(element.getName())) {
final Object factory = element.createExecutableExtension(classAttr);
if (!(factory instanceof IBuildParticipantFilterFactory)) {
DLTKCore.warn(
NLS.bind("{0} contributed by {1} must implement {2}",
new Object[] { element.getName(),
element.getContributor(),
IBuildParticipantFilterFactory.class
.getName() }));
return null;
}
return new FilterFactoryValue(
(IBuildParticipantFilterFactory) factory);
} else {
DLTKCore.warn(NLS.bind(
"Wrong element {0} in {1} extension point contributed by {2}",
new Object[] { element.getName(), extensionPoint,
element.getContributor() }));
return null;
}
}
private static BuildParticipantManager instance = null;
private static synchronized BuildParticipantManager getInstance() {
if (instance == null) {
instance = new BuildParticipantManager();
}
return instance;
}
private static final IBuildParticipant[] NO_PARTICIPANTS = new IBuildParticipant[0];
private static final IBuildParticipantFilter[] NO_PREDICATES = new IBuildParticipantFilter[0];
public static class BuildParticipantResult {
// not-null
public final IBuildParticipant[] participants;
// nullable
public final Map<IBuildParticipant, List<IBuildParticipant>> dependencies;
public BuildParticipantResult(IBuildParticipant[] participants,
Map<IBuildParticipant, List<IBuildParticipant>> dependencies) {
this.participants = participants;
this.dependencies = dependencies;
}
}
/**
* Returns {@link IBuildParticipant} instances of the specified nature. If
* there are no build participants then the empty array is returned.
*
* @param project
* @param natureId
* @return
*/
public static BuildParticipantResult getBuildParticipants(
IScriptProject project, String natureId) {
final FactoryValue<?>[] factories = getInstance()
.getInstances(natureId);
if (factories == null || factories.length == 0) {
return new BuildParticipantResult(NO_PARTICIPANTS, null);
}
return createParticipants(project, factories);
}
public static BuildParticipantResult createParticipants(
IScriptProject project, FactoryValue<?>[] factories) {
final IBuildParticipant[] result = new IBuildParticipant[factories.length];
final Set<String> processed = new HashSet<>();
final Map<String, IBuildParticipant> created = new HashMap<>();
final Map<IBuildParticipant, List<IBuildParticipant>> dependencies = new HashMap<>();
for (;;) {
final int iterationStartCount = created.size();
for (int i = 0; i < factories.length; ++i) {
if (!(factories[i] instanceof BuildParticipantFactoryValue)) {
continue;
}
final BuildParticipantFactoryValue factory = (BuildParticipantFactoryValue) factories[i];
if (!processed.contains(factory.id)
&& created.keySet().containsAll(factory.requirements)) {
processed.add(factory.id);
try {
final IBuildParticipant participant = factory.factory
.createBuildParticipant(project);
if (participant != null) {
result[created.size()] = participant;
created.put(factory.id, participant);
if (!factory.requirements.isEmpty()) {
for (String req : factory.requirements) {
final IBuildParticipant reqParticipant = created
.get(req);
List<IBuildParticipant> depList = dependencies
.get(reqParticipant);
if (depList == null) {
depList = new ArrayList<>();
dependencies.put(reqParticipant,
depList);
}
depList.add(participant);
}
}
}
} catch (CoreException e) {
final String tpl = Messages.BuildParticipantManager_buildParticipantCreateError;
DLTKCore.warn(NLS.bind(tpl, factory.id), e);
}
}
}
if (iterationStartCount == created.size()) {
break;
}
}
if (created.size() != result.length) {
if (created.size() == 0) {
return new BuildParticipantResult(NO_PARTICIPANTS, null);
}
final IBuildParticipant[] newResult = new IBuildParticipant[created
.size()];
System.arraycopy(result, 0, newResult, 0, created.size());
return new BuildParticipantResult(newResult, dependencies);
} else {
return new BuildParticipantResult(result, dependencies);
}
}
public static IBuildParticipantFilter[] getFilters(IScriptProject project,
String natureId, Object context) {
final FactoryValue<?>[] factories = getInstance()
.getInstances(natureId);
if (factories == null || factories.length == 0) {
return NO_PREDICATES;
}
return createFilters(project, factories, context);
}
public static IBuildParticipantFilter[] createFilters(
IScriptProject project, FactoryValue<?>[] factories,
Object context) {
final IBuildParticipantFilter[] result = new IBuildParticipantFilter[factories.length];
int created = 0;
for (int i = 0; i < factories.length; ++i) {
if (!(factories[i] instanceof FilterFactoryValue)) {
continue;
}
final FilterFactoryValue factory = (FilterFactoryValue) factories[i];
try {
final IBuildParticipantFilter filter = factory.factory
.createPredicate(project, context);
if (filter != null) {
result[created++] = filter;
}
} catch (CoreException e) {
final String tpl = Messages.BuildParticipantManager_buildParticipantCreateError;
DLTKCore.warn(
NLS.bind(tpl, factory.factory.getClass().getName()), e);
}
}
if (created != result.length) {
if (created == 0) {
return NO_PREDICATES;
}
final IBuildParticipantFilter[] newResult = new IBuildParticipantFilter[created];
System.arraycopy(result, 0, newResult, 0, created);
return newResult;
} else {
return result;
}
}
public static IBuildParticipant[] copyFirst(IBuildParticipant[] array,
int length) {
if (length == array.length) {
return array;
}
if (length == 0) {
return BuildParticipantManager.NO_PARTICIPANTS;
} else {
IBuildParticipant[] temp = new IBuildParticipant[length];
System.arraycopy(array, 0, temp, 0, length);
return temp;
}
}
/**
* First removes dangling {@link IBuildParticipant}s from
* {@link #dependencies} then notifies participants about their
* dependencies.
*
* @param dependencies
* @param participants
*/
public static void notifyDependents(IBuildParticipant[] participants,
Map<IBuildParticipant, List<IBuildParticipant>> dependencies) {
if (dependencies == null) {
return;
}
final List<IBuildParticipant> list = Arrays.asList(participants);
dependencies.keySet().retainAll(list);
for (Iterator<Map.Entry<IBuildParticipant, List<IBuildParticipant>>> i = dependencies
.entrySet().iterator(); i.hasNext();) {
final Entry<IBuildParticipant, List<IBuildParticipant>> entry = i
.next();
entry.getValue().retainAll(list);
if (entry.getValue().isEmpty()) {
i.remove();
}
}
if (dependencies.isEmpty()) {
return;
}
for (Map.Entry<IBuildParticipant, List<IBuildParticipant>> entry : dependencies
.entrySet()) {
if (entry.getKey() instanceof IBuildParticipantExtension4) {
final List<IBuildParticipant> dependents = entry.getValue();
((IBuildParticipantExtension4) entry.getKey())
.notifyDependents(dependents.toArray(
new IBuildParticipant[dependents.size()]));
}
}
}
}