| /******************************************************************************* |
| * Copyright (c) 2000, 2021 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Christoph Läubrich - Bug 576568 |
| *******************************************************************************/ |
| package org.eclipse.pde.internal.core; |
| |
| import java.io.BufferedInputStream; |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.net.URL; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.LinkedHashMap; |
| import java.util.List; |
| import java.util.Map; |
| import org.eclipse.core.runtime.CoreException; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.ListenerList; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.core.runtime.SubMonitor; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.pde.core.IModelProviderEvent; |
| import org.eclipse.pde.core.IModelProviderListener; |
| import org.eclipse.pde.core.target.ITargetDefinition; |
| import org.eclipse.pde.core.target.TargetFeature; |
| import org.eclipse.pde.internal.core.feature.ExternalFeatureModel; |
| import org.eclipse.pde.internal.core.ifeature.IFeatureModel; |
| import org.eclipse.pde.internal.core.target.Messages; |
| |
| /** |
| * Manages the features known to the PDE state that come from the target |
| * platform. |
| * <p> |
| * Contains utility methods to create feature models for locations. |
| * </p> |
| */ |
| public class ExternalFeatureModelManager { |
| |
| /** |
| * Creates a feature model for the feature based on the given feature XML |
| * file. |
| * |
| * @param manifest |
| * feature XML file in the local file system |
| * @return {@link ExternalFeatureModel} containing information loaded from |
| * the xml |
| * @throws CoreException |
| * if there is a problem reading the feature xml |
| */ |
| public static IFeatureModel createModel(File manifest) throws CoreException { |
| ExternalFeatureModel model = new ExternalFeatureModel(); |
| model.setInstallLocation(manifest.getParent()); |
| try (InputStream stream = new BufferedInputStream(new FileInputStream(manifest))) { |
| model.load(stream, false); |
| return model; |
| } catch (IOException e) { |
| throw new CoreException(Status.error(NLS.bind(Messages.TargetFeature_FileDoesNotExist, manifest))); |
| } |
| } |
| |
| private final ListenerList<IModelProviderListener> fListeners = new ListenerList<>(); |
| |
| private IFeatureModel[] fModels; |
| |
| public void addModelProviderListener(IModelProviderListener listener) { |
| fListeners.add(listener); |
| } |
| |
| private void fireModelProviderEvent(IModelProviderEvent e) { |
| for (IModelProviderListener listener : fListeners) { |
| listener.modelsChanged(e); |
| } |
| } |
| |
| /** |
| * Loads new feature models from preferences and notifies listeners. |
| */ |
| public void initialize() { |
| // Do the model loading in a synch block in case other changes cause the |
| // models to load |
| IFeatureModel[] oldModels = null; |
| synchronized (this) { |
| oldModels = fModels != null ? fModels : new IFeatureModel[0]; |
| long startTime = System.currentTimeMillis(); |
| fModels = getExternalModels(); |
| if (PDECore.DEBUG_MODEL) { |
| System.out.println( |
| "Time to load features from target platform (ms): " + (System.currentTimeMillis() - startTime)); //$NON-NLS-1$ |
| System.out.println("External features loaded: " + fModels.length); //$NON-NLS-1$ |
| } |
| } |
| // Release lock when notifying listeners. See bug 270891. |
| notifyListeners(oldModels, fModels); |
| } |
| |
| /** |
| * Returns a list of feature models from the target platform or an empty |
| * array if there is a problem. |
| * |
| * @return list of external feature models, possibly empty |
| */ |
| private IFeatureModel[] getExternalModels() { |
| // Plug-in resolution was cancelled by the user so skip resolving the |
| // target |
| if (PDECore.getDefault().getModelManager().isCancelled()) { |
| return new IFeatureModel[0]; |
| } |
| |
| ITargetDefinition target; |
| try { |
| target = TargetPlatformHelper.getWorkspaceTargetResolved(null); |
| } catch (CoreException e) { |
| PDECore.log(e); |
| return new IFeatureModel[0]; |
| } |
| |
| // Resolution cancelled |
| if (target == null) { |
| return new IFeatureModel[0]; |
| } |
| |
| List<IFeatureModel> result = new ArrayList<>(); |
| TargetFeature[] features = target.getAllFeatures(); |
| if (features != null) { |
| for (TargetFeature feature : features) { |
| IFeatureModel model = getFreshFeatureModel(feature); |
| if (model.isLoaded()) { |
| result.add(model); |
| } |
| } |
| } |
| return result.toArray(new IFeatureModel[result.size()]); |
| } |
| |
| private void notifyListeners(IFeatureModel[] oldModels, IFeatureModel[] newFeatureModels) { |
| if (oldModels.length > 0 || newFeatureModels.length > 0) { |
| int type = 0; |
| if (oldModels.length > 0) { |
| type |= IModelProviderEvent.MODELS_REMOVED; |
| } |
| if (newFeatureModels.length > 0) { |
| type |= IModelProviderEvent.MODELS_ADDED; |
| } |
| ModelProviderEvent replacedFeatures = new ModelProviderEvent(this, type, newFeatureModels, oldModels, null); |
| fireModelProviderEvent(replacedFeatures); |
| } |
| |
| } |
| |
| public void removeModelProviderListener(IModelProviderListener listener) { |
| fListeners.remove(listener); |
| } |
| |
| public IFeatureModel[] getModels() { |
| return fModels; |
| } |
| |
| /** |
| * Loads the model freshly from the location if it is is a valid file and |
| * can be read, otherwise returns the internal model |
| * |
| * @param feature |
| * the feature to load the model for |
| * @return the (possibly refreshed) model for this feature |
| */ |
| private static IFeatureModel getFreshFeatureModel(TargetFeature feature) { |
| String location = feature.getLocation(); |
| if (location != null) { |
| File manifest = new File(location, ICoreConstants.FEATURE_FILENAME_DESCRIPTOR); |
| if (manifest.exists() && manifest.isFile()) { |
| try { |
| return createModel(manifest); |
| } catch (CoreException e) { |
| PDECore.log(e); |
| } |
| } |
| } |
| // IFeatureModel is an internal class and thus can't be declared public |
| // but TargetFeature ensures the correct type so the cast is safe here. |
| return (IFeatureModel) feature.getFeatureModel(); |
| } |
| |
| public static TargetFeature[] createFeatures(String platformHome, ArrayList<String> additionalLocations, |
| IProgressMonitor monitor) { |
| if (platformHome != null && platformHome.length() > 0) { |
| URL[] featureURLs = PluginPathFinder.getFeaturePaths(platformHome); |
| |
| if (additionalLocations.isEmpty()) { |
| return createFeatures(featureURLs, monitor); |
| } |
| |
| File[] dirs = new File[additionalLocations.size()]; |
| for (int i = 0; i < dirs.length; i++) { |
| String directory = additionalLocations.get(i); |
| File dir = new File(directory, "features"); //$NON-NLS-1$ |
| if (!dir.exists()) { |
| dir = new File(directory); |
| } |
| dirs[i] = dir; |
| } |
| |
| URL[] newUrls = PluginPathFinder.scanLocations(dirs); |
| |
| URL[] result = new URL[featureURLs.length + newUrls.length]; |
| System.arraycopy(featureURLs, 0, result, 0, featureURLs.length); |
| System.arraycopy(newUrls, 0, result, featureURLs.length, newUrls.length); |
| return createFeatures(result, monitor); |
| } |
| return new TargetFeature[0]; |
| } |
| |
| private static TargetFeature[] createFeatures(URL[] featurePaths, IProgressMonitor monitor) { |
| SubMonitor subMonitor = SubMonitor.convert(monitor, featurePaths.length); |
| Map<String, TargetFeature> uniqueFeatures = new LinkedHashMap<>(); |
| for (URL featurePath : featurePaths) { |
| File manifest = new File(featurePath.getFile(), ICoreConstants.FEATURE_FILENAME_DESCRIPTOR); |
| if (!manifest.exists() || !manifest.isFile()) { |
| subMonitor.split(1); |
| continue; |
| } |
| try { |
| TargetFeature model = new TargetFeature(manifest); |
| uniqueFeatures.put(model.getId() + "_" + model.getVersion(), model); //$NON-NLS-1$ |
| } catch (CoreException e) { |
| // Ignore bad files in the collection |
| } |
| subMonitor.split(1); |
| } |
| Collection<TargetFeature> models = uniqueFeatures.values(); |
| return models.toArray(new TargetFeature[models.size()]); |
| } |
| } |