blob: 87ba9c9c5a52d5e1505d48df4cd1638f68374032 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008, 2016 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
* Manumitting Technologies Inc - bug 437726: wrong error messages opening target definition
*******************************************************************************/
package org.eclipse.pde.internal.core.target;
import java.io.*;
import java.util.*;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import org.eclipse.core.runtime.*;
import org.eclipse.core.variables.IStringVariableManager;
import org.eclipse.core.variables.VariablesPlugin;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.equinox.frameworkadmin.BundleInfo;
import org.eclipse.equinox.p2.metadata.Version;
import org.eclipse.osgi.util.NLS;
import org.eclipse.pde.core.plugin.TargetPlatform;
import org.eclipse.pde.core.target.*;
import org.eclipse.pde.internal.core.*;
import org.xml.sax.SAXException;
/**
* Target definition implementation.
*
* @since 3.5
*/
public class TargetDefinition implements ITargetDefinition {
// name and description
private String fName;
// included and optional filtering
private NameVersionDescriptor[] fIncluded;
// arguments
private String fProgramArgs;
private String fVMArgs;
// environment settings
private IPath fJREContainer;
private String fArch;
private String fOS;
private String fWS;
private String fNL;
// bundle containers
private ITargetLocation[] fContainers;
// handle
private ITargetHandle fHandle;
/**
* Status generated when this target was resolved, possibly <code>null</code>
*/
private IStatus fResolutionStatus;
// implicit dependencies
private NameVersionDescriptor[] fImplicit;
// internal settings for UI mode (how content is displayed to the user
private int fUIMode = MODE_PLUGIN;
public static final int MODE_PLUGIN = 0;
public static final int MODE_FEATURE = 1;
// cache of features found for a given location, maps a string path location to a array of IFeatureModels (IFeatureModel[])
private static Map<String, TargetFeature[]> fFeaturesInLocation = new HashMap<>();
// internal cache for features. A target managed by features will contain a set of features as well as a set of plug-ins that don't belong to a feature
private TargetFeature[] fFeatures;
private TargetBundle[] fOtherBundles;
private int fSequenceNumber = -1;
/**
* Constructs a target definition based on the given handle.
*/
TargetDefinition(ITargetHandle handle) {
fHandle = handle;
}
@Override
public String getArch() {
return fArch;
}
@Override
public ITargetLocation[] getTargetLocations() {
return fContainers;
}
@Override
public String getNL() {
return fNL;
}
@Override
public String getName() {
return fName;
}
@Override
public String getOS() {
return fOS;
}
@Override
public String getProgramArguments() {
return fProgramArgs;
}
@Override
public String getVMArguments() {
return fVMArgs;
}
@Override
public String getWS() {
return fWS;
}
@Override
public void setArch(String arch) {
incrementSequenceNumber();
fArch = arch;
}
@Override
public void setNL(String nl) {
incrementSequenceNumber();
fNL = nl;
}
@Override
public void setName(String name) {
fName = name;
}
@Override
public void setOS(String os) {
incrementSequenceNumber();
fOS = os;
}
@Override
public void setProgramArguments(String args) {
if (args != null && args.length() == 0) {
args = null;
}
fProgramArgs = args;
}
@Override
public void setVMArguments(String args) {
if (args != null && args.length() == 0) {
args = null;
}
fVMArgs = args;
}
@Override
public void setWS(String ws) {
incrementSequenceNumber();
fWS = ws;
}
@Override
public void setTargetLocations(ITargetLocation[] locations) {
incrementSequenceNumber();
// Clear the feature model cache as it is based on the bundle container locations
fFeatures = null;
fOtherBundles = null;
if (locations != null && locations.length == 0) {
locations = null;
}
fContainers = locations;
if (locations == null) {
fIncluded = null;
} else {
for (int i = 0; i < locations.length; i++) {
if (locations[i] instanceof AbstractBundleContainer) {
((AbstractBundleContainer) locations[i]).associateWithTarget(this);
}
}
}
}
/**
* Clears the any models that are cached for the given container location.
*
* @param location location to clear cache for or <code>null</code> to clear all cached models
*/
public void flushCaches(String location) {
// Clear the feature model cache as it is based on the bundle container locations
fFeatures = null;
fOtherBundles = null;
if (location == null) {
fFeaturesInLocation.clear();
} else {
fFeaturesInLocation.remove(location);
}
if (fContainers == null) {
fIncluded = null;
}
}
@Override
public IStatus resolve(IProgressMonitor monitor) {
ITargetLocation[] containers = getTargetLocations();
int num = 0;
// keep a map of synchronizer and number of containers it synchronizes
HashMap<P2TargetUtils, Integer> synchronizerNumContainerMap = new HashMap<>();
if (containers != null) {
num = containers.length;
for (ITargetLocation element : containers) {
P2TargetUtils synchronizer = element.getAdapter(P2TargetUtils.class);
if (synchronizer != null) {
if (!synchronizerNumContainerMap.containsKey(synchronizer)) {
synchronizerNumContainerMap.put(synchronizer, Integer.valueOf(1));
}
else{
Integer numberIU = synchronizerNumContainerMap.get(synchronizer);
synchronizerNumContainerMap.put(synchronizer, Integer.valueOf(numberIU + 1));
}
}
}
}
fResolutionStatus = null;
SubMonitor subMonitor = SubMonitor.convert(monitor, Messages.TargetDefinition_1, num * 100);
try {
MultiStatus status = new MultiStatus(PDECore.PLUGIN_ID, 0, Messages.TargetDefinition_2, null);
Set<P2TargetUtils> seen = new HashSet<>();
if (containers != null) {
// Process synchronizers first, then perform resolves against the individual
// containers. A synchronizer may be shared among several containers, do we
// keep track of the synchronizers processed.
for (int i = 0; i < containers.length; i++) {
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
subMonitor.subTask(Messages.TargetDefinition_4);
P2TargetUtils synchronizer = containers[i].getAdapter(P2TargetUtils.class);
if (synchronizer != null && !seen.contains(synchronizer)) {
seen.add(synchronizer);
try {
synchronizer.synchronize(this,
subMonitor.split(synchronizerNumContainerMap.get(synchronizer).intValue() * 95));
} catch (CoreException e) {
status.add(e.getStatus());
}
}
}
synchronizerNumContainerMap.clear();
if (!status.isOK()) {
return fResolutionStatus = status;
}
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
for (int i = 0; i < containers.length; i++) {
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
subMonitor.subTask(Messages.TargetDefinition_4);
P2TargetUtils synchronizer = containers[i].getAdapter(P2TargetUtils.class);
int totalWork = 5;
if (synchronizer == null)
totalWork = 100;
IStatus s = containers[i].resolve(this, subMonitor.split(totalWork));
if (!s.isOK()) {
status.add(s);
}
}
}
if (status.isOK()) {
return fResolutionStatus = Status.OK_STATUS;
}
if (subMonitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
return fResolutionStatus = status;
} finally {
// keep a list of resolved targets with key as handle
TargetPlatformHelper.addTargetDefinitionMap(this);
subMonitor.done();
if (monitor != null) {
monitor.done();
}
}
}
@Override
public boolean isResolved() {
ITargetLocation[] containers = getTargetLocations();
if (containers != null) {
for (int i = 0; i < containers.length; i++) {
ITargetLocation container = containers[i];
if (!container.isResolved()) {
return false;
}
}
}
return true;
}
@Override
public IStatus getStatus() {
if (fResolutionStatus != null && !fResolutionStatus.isOK()) {
return fResolutionStatus;
}
if (isResolved()) {
ITargetLocation[] containers = getTargetLocations();
if (containers != null) {
// Check if the containers have any resolution problems
MultiStatus result = new MultiStatus(PDECore.PLUGIN_ID, 0, Messages.TargetDefinition_5, null);
for (int i = 0; i < containers.length; i++) {
ITargetLocation container = containers[i];
IStatus containerStatus = container.getStatus();
if (containerStatus != null && !containerStatus.isOK()) {
result.add(containerStatus);
}
}
// Check if any of the included bundles have problems
// build status from bundle list
TargetBundle[] bundles = getBundles();
for (int i = 0; i < bundles.length; i++) {
if (!bundles[i].getStatus().isOK()) {
result.add(bundles[i].getStatus());
}
}
if (result.isOK()) {
// Return generic ok status instead of problem multi-status with no children
return Status.OK_STATUS;
}
return result;
}
return Status.OK_STATUS;
}
return null;
}
@Override
public void setIncluded(NameVersionDescriptor[] included) {
fIncluded = included;
}
@Override
public NameVersionDescriptor[] getIncluded() {
return fIncluded;
}
@Override
public TargetBundle[] getBundles() {
return getBundles(false);
}
@Override
public TargetBundle[] getAllBundles() {
return getBundles(true);
}
/**
* Gathers and returns all or included bundles in this target or <code>null</code> if
* not resolved.
*
* @param allBundles whether to consider all bundles, or just those included/optional
* @return bundles or <code>null</code>
*/
private TargetBundle[] getBundles(boolean allBundles) {
if (isResolved()) {
ITargetLocation[] containers = getTargetLocations();
if (containers != null) {
List<TargetBundle> all = new ArrayList<>();
for (int i = 0; i < containers.length; i++) {
ITargetLocation container = containers[i];
TargetBundle[] bundles = container.getBundles();
if (bundles != null) {
for (int j = 0; j < bundles.length; j++) {
TargetBundle rb = bundles[j];
all.add(rb);
}
}
}
TargetBundle[] allResolvedBundles = all.toArray(new TargetBundle[all.size()]);
if (allBundles) {
return allResolvedBundles;
}
return filterBundles(allResolvedBundles, getIncluded());
}
return new TargetBundle[0];
}
return null;
}
private TargetBundle[] filterBundles(TargetBundle[] bundles, NameVersionDescriptor[] filter) {
if (filter == null) {
// No filtering to do
return bundles;
}
if (filter.length == 0) {
return new TargetBundle[0];
}
// If there are features, don't set errors for missing bundles as they are caused by missing OS specific fragments
boolean containsFeatures = false;
// If there are any included features that are missing, add errors as resolved bundles (the same thing we would do for missing bundles)
List<NameVersionDescriptor> missingFeatures = new ArrayList<>();
List<NameVersionDescriptor> included = new ArrayList<>();
// For feature filters, get the list of included bundles, for bundle filters just add them to the list
for (int i = 0; i < filter.length; i++) {
if (filter[i].getType() == NameVersionDescriptor.TYPE_PLUGIN) {
included.add(filter[i]);
} else if (filter[i].getType() == NameVersionDescriptor.TYPE_FEATURE) {
containsFeatures = true;
TargetFeature[] features = getAllFeatures();
TargetFeature bestMatch = null;
for (int j = 0; j < features.length; j++) {
TargetFeature feature = features[j];
if (feature.getId().equals(filter[i].getId())) {
if (filter[i].getVersion() != null) {
// Try to find an exact feature match
if (filter[i].getVersion().equals(feature.getVersion())) {
// Exact match
bestMatch = feature;
break;
}
} else if (bestMatch != null) {
// If no version specified take the highest version
Version v1 = Version.parseVersion(feature.getVersion());
Version v2 = Version.parseVersion(bestMatch.getVersion());
if (v1.compareTo(v2) > 0) {
bestMatch = feature;
}
}
if (bestMatch == null) {
// If we can't find a version match, just take any name match
bestMatch = feature;
}
}
}
// Add the required plugins from the feature to the list of includes
if (bestMatch != null) {
NameVersionDescriptor[] plugins = bestMatch.getPlugins();
for (int j = 0; j < plugins.length; j++) {
included.add(plugins[j]);
}
} else {
missingFeatures.add(filter[i]);
}
}
}
// Return matching bundles, if we are organizing by feature, do not create invalid target bundles for missing bundle includes
List<TargetBundle> result = getMatchingBundles(bundles, included.toArray(new NameVersionDescriptor[included.size()]), !containsFeatures);
// Add in missing features as resolved bundles with error statuses
if (containsFeatures && !missingFeatures.isEmpty()) {
for (Iterator<NameVersionDescriptor> iterator = missingFeatures.iterator(); iterator.hasNext();) {
NameVersionDescriptor missing = iterator.next();
BundleInfo info = new BundleInfo(missing.getId(), missing.getVersion(), null, BundleInfo.NO_LEVEL, false);
String message = NLS.bind(Messages.TargetDefinition_RequiredFeatureCouldNotBeFound, missing.getId());
Status status = new Status(IStatus.ERROR, PDECore.PLUGIN_ID, TargetBundle.STATUS_FEATURE_DOES_NOT_EXIST, message, null);
result.add(new InvalidTargetBundle(info, status));
}
}
return result.toArray(new TargetBundle[result.size()]);
}
/**
* Returns bundles from the specified collection that match the symbolic names
* and/or version in the specified criteria. When no version is specified
* the newest version (if any) is selected.
* <p>
* If handleMissingBundles is <code>true</code>, the returned list will contain {@link InvalidTargetBundle}s
* for any included filters that do not have a matching bundle in the collection. The invalid bundles
* will contain statuses describing what couldn't be matched.
* </p>
* @param collection bundles to resolve against match criteria
* @param included bundles to include or <code>null</code> if no restrictions
* @param handleMissingBundles whether to create {@link InvalidTargetBundle}s for missing includes
*
* @return list of IResolvedBundle bundles that match this container's restrictions
*/
static List<TargetBundle> getMatchingBundles(TargetBundle[] collection, NameVersionDescriptor[] included, boolean handleMissingBundles) {
if (included == null) {
ArrayList<TargetBundle> result = new ArrayList<>();
result.addAll(Arrays.asList(collection));
return result;
}
// map bundles names to available versions
Map<String, List<TargetBundle>> bundleMap = new HashMap<>(collection.length);
for (int i = 0; i < collection.length; i++) {
TargetBundle resolved = collection[i];
List<TargetBundle> list = bundleMap.get(resolved.getBundleInfo().getSymbolicName());
if (list == null) {
list = new ArrayList<>(3);
bundleMap.put(resolved.getBundleInfo().getSymbolicName(), list);
}
list.add(resolved);
}
List<TargetBundle> resolved = new ArrayList<>();
for (int i = 0; i < included.length; i++) {
BundleInfo info = new BundleInfo(included[i].getId(), included[i].getVersion(), null, BundleInfo.NO_LEVEL, false);
TargetBundle bundle = resolveBundle(bundleMap, info, handleMissingBundles);
if (bundle != null) {
resolved.add(bundle);
}
}
return resolved;
}
/**
* Resolves a bundle for the given info from the given map. The map contains
* keys of symbolic names and values are lists of {@link TargetBundle}'s available
* that match the names.
* <p>
* If handleMissingBundles is <code>true</code>, a {@link InvalidTargetBundle} will be created and
* returned if the give info does not match up with a map entry. The returned bundle will have
* a status giving more details on what is missing. If handleMissingBundles is <code>false</code>,
* <code>null</code> will be returned.
* </p>
*
* @param bundleMap available bundles to resolve against
* @param info name and version to match against
* @param handleMissingBundles whether to return an {@link InvalidTargetBundle} for a info that does not match with a map entry or <code>null</code>
* @return resolved bundle or <code>null</code>
*/
private static TargetBundle resolveBundle(Map<String, List<TargetBundle>> bundleMap, BundleInfo info, boolean handleMissingBundles) {
List<TargetBundle> list = bundleMap.get(info.getSymbolicName());
if (list != null) {
String version = info.getVersion();
if (version == null || version.equals(BundleInfo.EMPTY_VERSION)) {
// select newest
if (list.size() > 1) {
// sort the list
Collections.sort(list, new Comparator<Object>() {
@Override
public int compare(Object o1, Object o2) {
BundleInfo b1 = ((TargetBundle) o1).getBundleInfo();
BundleInfo b2 = ((TargetBundle) o2).getBundleInfo();
try {
Version v1 = Version.create(b1.getVersion());
Version v2 = Version.create(b2.getVersion());
return v1.compareTo(v2);
} catch (IllegalArgumentException e) {
// If one of the bundles has a bad version
PDECore.log(e);
return b1.getVersion().compareTo(b2.getVersion());
}
}
});
}
// select the last one
TargetBundle rb = list.get(list.size() - 1);
return rb;
}
Iterator<?> iterator = list.iterator();
while (iterator.hasNext()) {
TargetBundle bundle = (TargetBundle) iterator.next();
if (bundle.getBundleInfo().getVersion().equals(version)) {
return bundle;
}
}
// VERSION DOES NOT EXIST
if (!handleMissingBundles) {
return null;
}
int sev = IStatus.ERROR;
String message = NLS.bind(Messages.AbstractBundleContainer_1, new Object[] {info.getVersion(), info.getSymbolicName()});
IStatus status = new Status(sev, PDECore.PLUGIN_ID, TargetBundle.STATUS_VERSION_DOES_NOT_EXIST, message, null);
return new InvalidTargetBundle(info, status);
}
// DOES NOT EXIST
if (!handleMissingBundles) {
return null;
}
int sev = IStatus.ERROR;
String message = NLS.bind(Messages.AbstractBundleContainer_3, info.getSymbolicName());
IStatus status = new Status(sev, PDECore.PLUGIN_ID, TargetBundle.STATUS_PLUGIN_DOES_NOT_EXIST, message, null);
return new InvalidTargetBundle(info, status);
}
@Override
public ITargetHandle getHandle() {
return fHandle;
}
/**
* Build contents from the given stream.
*
* @param stream input stream
* @throws CoreException if an error occurs
*/
void setContents(InputStream stream) throws CoreException {
try {
fArch = null;
fContainers = null;
fImplicit = null;
fJREContainer = null;
fIncluded = null;
fName = null;
fNL = null;
fOS = null;
fProgramArgs = null;
fVMArgs = null;
fWS = null;
fSequenceNumber = 0;
TargetDefinitionPersistenceHelper.initFromXML(this, stream);
} catch (ParserConfigurationException e) {
abort(Messages.TargetDefinition_0, e);
} catch (SAXException e) {
abort(Messages.TargetDefinition_0, e);
} catch (IOException e) {
abort(Messages.TargetDefinition_0, e);
}
}
/**
* Persists contents to the given stream.
*
* @param stream output stream
* @throws CoreException if an error occurs
*/
void write(OutputStream stream) throws CoreException {
try {
TargetDefinitionPersistenceHelper.persistXML(this, stream);
} catch (IOException e) {
abort(Messages.TargetDefinition_3, e);
} catch (ParserConfigurationException e) {
abort(Messages.TargetDefinition_3, e);
} catch (TransformerException e) {
abort(Messages.TargetDefinition_3, e);
} catch (SAXException e) {
abort(Messages.TargetDefinition_3, e);
}
}
/**
* Throws a core exception with the given message and underlying exception (possibly
* <code>null</code>).
*
* @param message message
* @param e underlying cause of the exception or <code>null</code>
* @throws CoreException
*/
private void abort(String message, Exception e) throws CoreException {
throw new CoreException(new Status(IStatus.ERROR, PDECore.PLUGIN_ID, message, e));
}
@Override
public NameVersionDescriptor[] getImplicitDependencies() {
return fImplicit;
}
@Override
public void setImplicitDependencies(NameVersionDescriptor[] bundles) {
if (bundles != null && bundles.length == 0) {
bundles = null;
}
fImplicit = bundles;
}
@Override
public IPath getJREContainer() {
return fJREContainer;
}
@Override
public void setJREContainer(IPath containerPath) {
fJREContainer = containerPath;
}
/**
* Returns whether the content of this definition is equal to the content of the specified definition.
*
* @param definition
* @return whether the content of this definition is equal to the content of the specified definition
*/
public boolean isContentEqual(ITargetDefinition definition) {
if (isNullOrEqual(getName(), definition.getName()) && isNullOrEqual(getArch(), definition.getArch()) && isNullOrEqual(getNL(), definition.getNL()) && isNullOrEqual(getOS(), definition.getOS()) && isNullOrEqual(getWS(), definition.getWS()) && isNullOrEqual(getProgramArguments(), definition.getProgramArguments()) && isNullOrEqual(getVMArguments(), definition.getVMArguments()) && isNullOrEqual(getJREContainer(), definition.getJREContainer())) {
// Check includes/optional
if (isNullOrEqual(getIncluded(), definition.getIncluded())) {
// Check containers
ITargetLocation[] c1 = getTargetLocations();
ITargetLocation[] c2 = definition.getTargetLocations();
if (areContainersEqual(c1, c2)) {
// Check implicit dependencies
return isNullOrEqual(getImplicitDependencies(), definition.getImplicitDependencies());
}
}
}
return false;
}
/**
* Returns whether the content of this definition is equivalent to the content of the
* specified definition (excluding name/description).
*
* @param definition
* @return whether the content of this definition is equivalent to the content of the
* specified definition
*/
public boolean isContentEquivalent(ITargetDefinition definition) {
if (isNullOrEqual(getArch(), definition.getArch()) && isNullOrEqual(getNL(), definition.getNL()) && isNullOrEqual(getOS(), definition.getOS()) && isNullOrEqual(getWS(), definition.getWS())) {
if (isArgsNullOrEqual(getProgramArguments(), definition.getProgramArguments()) && isArgsNullOrEqual(getVMArguments(), definition.getVMArguments()) && isNullOrEqual(getJREContainer(), definition.getJREContainer())) {
// Check includes/optional
if (isNullOrEqual(getIncluded(), definition.getIncluded())) {
// Check containers
ITargetLocation[] c1 = getTargetLocations();
ITargetLocation[] c2 = definition.getTargetLocations();
if (areContainersEqual(c1, c2)) {
// Check implicit dependencies
return isNullOrEqual(getImplicitDependencies(), definition.getImplicitDependencies());
}
}
}
}
return false;
}
private boolean isNullOrEqual(Object o1, Object o2) {
if (o1 == null) {
return o2 == null;
}
if (o2 == null) {
return false;
}
return o1.equals(o2);
}
/**
* Returns whether the arrays have equal contents or are both <code>null</code>.
*
* @param objects1
* @param objects2
* @return whether the arrays have equal contents or are both <code>null</code>
*/
private boolean isNullOrEqual(Object[] objects1, Object[] objects2) {
if (objects1 == null) {
return objects2 == null;
}
if (objects2 == null) {
return false;
}
if (objects1.length == objects2.length) {
for (int i = 0; i < objects1.length; i++) {
if (!objects1[i].equals(objects2[i])) {
return false;
}
}
return true;
}
return false;
}
private boolean isArgsNullOrEqual(String args1, String args2) {
if (args1 == null) {
return args2 == null;
}
if (args2 == null) {
return false;
}
String[] a1 = DebugPlugin.parseArguments(args1);
String[] a2 = DebugPlugin.parseArguments(args2);
if (a1.length == a2.length) {
for (int i = 0; i < a1.length; i++) {
if (!a1[i].equals(a2[i])) {
return false;
}
}
return true;
}
return false;
}
private boolean areContainersEqual(ITargetLocation[] c1, ITargetLocation[] c2) {
if (c1 == null) {
return c2 == null;
}
if (c2 == null) {
return false;
}
if (c1.length == c2.length) {
for (int i = 0; i < c2.length; i++) {
if (!c1[i].equals(c2[i])) {
return false;
}
}
return true;
}
return false;
}
@Override
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append(fName != null ? fName : "No Name"); //$NON-NLS-1$
if (fContainers == null) {
buf.append("\n\tNo containers"); //$NON-NLS-1$
} else {
for (int i = 0; i < fContainers.length; i++) {
buf.append("\n\t").append(fContainers[i].toString()); //$NON-NLS-1$
}
}
buf.append("\nEnv: ").append(fOS).append("/").append(fWS).append("/").append(fArch).append("/").append(fNL); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
buf.append("\nJRE: ").append(fJREContainer); //$NON-NLS-1$
buf.append("\nArgs: ").append(fProgramArgs).append("/").append(fVMArgs); //$NON-NLS-1$ //$NON-NLS-2$
buf.append("\nImplicit: ").append(fImplicit == null ? "null" : Integer.toString(fImplicit.length)); //$NON-NLS-1$ //$NON-NLS-2$
buf.append("\nHandle: ").append(fHandle.toString()); //$NON-NLS-1$
return buf.toString();
}
/**
* Returns a set of feature models that exist in the provided location. If
* the locationPath is <code>null</code> the default target platform location
* will be used. The locationPath string may container string variables which
* will be resolved. This target definition may cache the feature models for
* faster retrieval.
*
* TODO When to clear the cache
*
* @param locationPath string path to the directory containing features. May container string variables or be <code>null</code>
* @return list of feature models found in the location, possible empty
* @param monitor progress monitor
* @throws CoreException if there is a problem substituting a string variable
*/
public TargetFeature[] resolveFeatures(String locationPath, IProgressMonitor monitor) throws CoreException {
String path = locationPath;
if (path == null) {
path = TargetPlatform.getDefaultLocation();
} else {
IStringVariableManager manager = VariablesPlugin.getDefault().getStringVariableManager();
path = manager.performStringSubstitution(path);
}
TargetFeature[] models = null;
if (fFeaturesInLocation != null) {
models = fFeaturesInLocation.get(path);
}
if (models != null) {
return models; /*(IFeatureModel[])models.toArray(new IFeatureModel[models.size()]);*/
}
models = ExternalFeatureModelManager.createFeatures(path, new ArrayList<>(), monitor);
fFeaturesInLocation.put(path, models);
return models;
}
@Override
public TargetFeature[] getAllFeatures() {
if (!isResolved()) {
return null;
}
if (fFeatures != null) {
return fFeatures;
}
ITargetLocation[] containers = getTargetLocations();
// collect up all features from all containers and remove duplicates.
Map<NameVersionDescriptor, TargetFeature> result = new HashMap<>();
if (containers != null && containers.length > 0) {
for (int i = 0; i < containers.length; i++) {
TargetFeature[] currentFeatures = containers[i].getFeatures();
if (currentFeatures != null && currentFeatures.length > 0) {
for (int j = 0; j < currentFeatures.length; j++) {
TargetFeature feature = currentFeatures[j];
if (feature.getId() != null) {
// Don't allow features with null ids, Bug 377563
NameVersionDescriptor key = new NameVersionDescriptor(feature.getId(), feature.getVersion());
result.put(key, feature);
}
}
}
}
}
fFeatures = result.values().toArray(new TargetFeature[result.size()]);
return fFeatures;
}
/**
* Returns the set of IResolvedBundle available in this target that are not part of any features, will return a cached copy if available
*
* @see #getAllFeatures()
* @return set of resolved bundles available in this target that don't belong to any features, possibly empty
*/
public TargetBundle[] getOtherBundles() {
if (!isResolved()) {
return null;
}
if (fOtherBundles != null) {
return fOtherBundles;
}
TargetBundle[] allBundles = getAllBundles();
Map<String, TargetBundle> remaining = new HashMap<>();
for (int i = 0; i < allBundles.length; i++) {
remaining.put(allBundles[i].getBundleInfo().getSymbolicName(), allBundles[i]);
}
TargetFeature[] features = getAllFeatures();
for (int i = 0; i < features.length; i++) {
NameVersionDescriptor[] plugins = features[i].getPlugins();
for (int j = 0; j < plugins.length; j++) {
remaining.remove(plugins[j].getId());
}
}
Collection<TargetBundle> values = remaining.values();
fOtherBundles = values.toArray(new TargetBundle[values.size()]);
return fOtherBundles;
}
/**
* Convenience method to return the set of {@link TargetFeature}s that are included in this
* target as well as any other included plug-ins as {@link TargetBundle}s (that are not part
* of the features). Also returns any bundles with error statuses. Will return <code>null</code>
* if this target has not been resolved.
*
* @see #getAllFeatures()
* @see #getOtherBundles()
* @return set of {@link TargetFeature}s and {@link TargetBundle}s or <code>null</code>
*/
public Set<Object> getFeaturesAndBundles() {
if (!isResolved()) {
return null;
}
TargetFeature[] allFeatures = getAllFeatures();
TargetBundle[] allExtraBundles = getOtherBundles();
NameVersionDescriptor[] included = getIncluded();
if (included == null) {
Set<Object> result = new HashSet<>();
result.addAll(Arrays.asList(allFeatures));
result.addAll(Arrays.asList(allExtraBundles));
return result;
}
Set<Object> result = new HashSet<>();
for (int i = 0; i < included.length; i++) {
if (included[i].getType() == NameVersionDescriptor.TYPE_PLUGIN) {
for (int j = 0; j < allExtraBundles.length; j++) {
if (allExtraBundles[j].getBundleInfo().getSymbolicName().equals(included[i].getId())) {
result.add(allExtraBundles[j]);
}
}
} else if (included[i].getType() == NameVersionDescriptor.TYPE_FEATURE) {
for (int j = 0; j < allFeatures.length; j++) {
if (allFeatures[j].getId().equals(included[i].getId())) {
result.add(allFeatures[j]);
}
}
}
}
return result;
}
/**
* @return the current UI style one of {@link #MODE_FEATURE} or {@link #MODE_PLUGIN}
*/
public int getUIMode() {
return fUIMode;
}
/**
* @param mode new UI style to use, one of {@link #MODE_FEATURE} or {@link #MODE_PLUGIN}
*/
public void setUIMode(int mode) {
fUIMode = mode;
}
/**
* Returns the current sequence number of this target. Sequence numbers change
* whenever something in the target that affects the set of features and bundles that
* would be resolved.
*
* @return the current sequence number
*/
public int getSequenceNumber() {
return fSequenceNumber;
}
/**
* Increases the current sequence number.
* @see TargetDefinition#getSequenceNumber()
* @return the current sequence number after it has been increased
*/
public int incrementSequenceNumber() {
return ++fSequenceNumber;
}
/**
* Convenience method to set the sequence number to a specific
* value. Used when loading a target from a persisted file.
* @param value value to set the sequence number to
*/
void setSequenceNumber(int value) {
fSequenceNumber = value;
}
}