diff --git a/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF
index 3f2bee839e..efad311560 100644
--- a/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.core/META-INF/MANIFEST.MF
@@ -80,6 +80,7 @@ Export-Package:
org.eclipse.pde.unittest.junit",
org.eclipse.pde.internal.core.variables;x-internal:=true
Import-Package: aQute.bnd.build;version="[4.4.0,5.0.0)",
+ aQute.bnd.header;version="2.5.0",
aQute.bnd.osgi;version="[5.5.0,6.0.0)",
aQute.bnd.osgi.repository;version="[3.0.0,4.0.0)",
aQute.bnd.osgi.resource;version="[4.3.0,5.0.0)",
diff --git a/ui/org.eclipse.pde.core/plugin.xml b/ui/org.eclipse.pde.core/plugin.xml
index 0db7b0acc4..98dbd6bea9 100644
--- a/ui/org.eclipse.pde.core/plugin.xml
+++ b/ui/org.eclipse.pde.core/plugin.xml
@@ -437,4 +437,12 @@
class="org.eclipse.pde.internal.core.LocalMavenPluginSourcePathLocator"
complexity="low"/>
+
+
+
+
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryBundleContainer.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryBundleContainer.java
new file mode 100644
index 0000000000..7cb47c74da
--- /dev/null
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryBundleContainer.java
@@ -0,0 +1,185 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.core.target;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.transform.OutputKeys;
+import javax.xml.transform.Transformer;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.dom.DOMSource;
+import javax.xml.transform.stream.StreamResult;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.FileLocator;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.equinox.internal.p2.repository.CacheManager;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.TargetBundle;
+import org.eclipse.pde.core.target.TargetFeature;
+import org.eclipse.pde.internal.core.PDECore;
+import org.eclipse.pde.internal.core.util.PDEXmlProcessorFactory;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+import aQute.bnd.osgi.repository.ResourcesRepository;
+import aQute.bnd.osgi.repository.XMLResourceParser;
+import aQute.bnd.osgi.resource.ResourceUtils;
+import aQute.bnd.osgi.resource.ResourceUtils.ContentCapability;
+
+@SuppressWarnings("restriction")
+public class RepositoryBundleContainer extends AbstractBundleContainer {
+ public static final String ATTRIBUTE_URI = "uri"; //$NON-NLS-1$
+
+ public static final String ELEMENT_REQUIRE = "require"; //$NON-NLS-1$
+
+ public static final String TYPE = "Repository"; //$NON-NLS-1$
+
+ private final String uri;
+
+ private final Collection requirements;
+
+ public RepositoryBundleContainer(String uri, Collection requirements) {
+ this.uri = uri;
+ this.requirements = requirements;
+ }
+
+ @Override
+ protected TargetBundle[] resolveBundles(ITargetDefinition definition, IProgressMonitor monitor)
+ throws CoreException {
+ ResourcesRepository repository = getRepository(monitor);
+ Map> providers = repository.findProviders(getRequirements());
+ List bundles = new ArrayList<>();
+ List contentCapabilities = providers.values().stream().flatMap(Collection::stream)
+ .map(Capability::getResource).distinct().map(ResourceUtils::getContentCapability)
+ .filter(Objects::nonNull).toList();
+ CacheManager cacheManager = getCacheManager();
+ for (ContentCapability content : contentCapabilities) {
+ URI url = content.url();
+ try {
+ File file;
+ if (cacheManager != null) {
+ file = cacheManager.createCacheFromFile(url, monitor);
+ } else {
+ file = new File(FileLocator.toFileURL(url.toURL()).toURI());
+ }
+ bundles.add(new TargetBundle(file));
+ } catch (IOException | URISyntaxException e) {
+ throw new CoreException(Status.error("Can't fetch bundle from " + url, e));
+ }
+ }
+ return bundles.toArray(TargetBundle[]::new);
+ }
+
+ public String getUri() {
+ return uri;
+ }
+
+ public Collection getRequirements() {
+ return requirements;
+ }
+
+ public ResourcesRepository getRepository(IProgressMonitor monitor) throws CoreException {
+ String location = getLocation(true);
+ try {
+ URI base = new URI(location);
+ try {
+ CacheManager cacheManager = getCacheManager();
+ if (cacheManager != null) {
+ File file = cacheManager.createCacheFromFile(base, monitor);
+ return new ResourcesRepository(XMLResourceParser.getResources(file, base));
+ }
+ return new ResourcesRepository(XMLResourceParser.getResources(base));
+ } catch (Exception e) {
+ if (e instanceof CoreException core) {
+ throw core;
+ }
+ if (e instanceof RuntimeException runtime) {
+ throw runtime;
+ }
+ throw new CoreException(Status.error("Loading repository from " + location + " failed: " + e, e));
+ }
+ } catch (URISyntaxException e) {
+ throw new CoreException(Status.error("Invalid URI: " + location, e));
+ }
+ }
+
+ private CacheManager getCacheManager() throws CoreException {
+ return P2TargetUtils.getAgent().getService(CacheManager.class);
+ }
+
+ @Override
+ protected TargetFeature[] resolveFeatures(ITargetDefinition definition, IProgressMonitor monitor)
+ throws CoreException {
+ return new TargetFeature[0];
+ }
+
+ @Override
+ public String getType() {
+ return TYPE;
+ }
+
+ @Override
+ public String getLocation(boolean resolve) throws CoreException {
+ if (resolve) {
+ return resolveVariables(uri);
+ }
+ return uri;
+ }
+
+ @Override
+ public String serialize() {
+ try {
+ DocumentBuilder docBuilder = PDEXmlProcessorFactory.createDocumentBuilderWithErrorOnDOCTYPE();
+ Document document = docBuilder.newDocument();
+ Element containerElement = document.createElement(TargetDefinitionPersistenceHelper.LOCATION);
+ containerElement.setAttribute(TargetDefinitionPersistenceHelper.ATTR_LOCATION_TYPE, TYPE);
+ containerElement.setAttribute(ATTRIBUTE_URI, getUri());
+ for (Requirement requirement : requirements) {
+ Element requireElement = document.createElement(ELEMENT_REQUIRE);
+ requireElement.setTextContent(requirement.toString());
+ containerElement.appendChild(requireElement);
+ }
+ document.appendChild(containerElement);
+ StreamResult result = new StreamResult(new StringWriter());
+ TransformerFactory f = PDEXmlProcessorFactory.createTransformerFactoryWithErrorOnDOCTYPE();
+ Transformer transformer = f.newTransformer();
+ transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes"); //$NON-NLS-1$
+ transformer.transform(new DOMSource(document), result);
+ String xml = result.getWriter().toString();
+ return xml;
+ } catch (Exception e) {
+ PDECore.log(e);
+ return null;
+ }
+ }
+
+ public void reload() {
+ fResolutionStatus = null;
+ fBundles = null;
+ }
+}
diff --git a/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryLocationFactory.java b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryLocationFactory.java
new file mode 100644
index 0000000000..4cdd48268a
--- /dev/null
+++ b/ui/org.eclipse.pde.core/src/org/eclipse/pde/internal/core/target/RepositoryLocationFactory.java
@@ -0,0 +1,68 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.core.target;
+
+import java.io.ByteArrayInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.stream.IntStream;
+
+import javax.xml.parsers.DocumentBuilder;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.core.target.ITargetLocationFactory;
+import org.eclipse.pde.internal.core.util.PDEXmlProcessorFactory;
+import org.osgi.resource.Requirement;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.NodeList;
+
+import aQute.bnd.header.Parameters;
+import aQute.bnd.osgi.resource.CapReqBuilder;
+
+public class RepositoryLocationFactory implements ITargetLocationFactory {
+
+ @Override
+ public ITargetLocation getTargetLocation(String type, String serializedXML) throws CoreException {
+ if (!RepositoryBundleContainer.TYPE.equals(type)) {
+ throw new CoreException(
+ Status.error(NLS.bind(Messages.TargetRefrenceLocationFactory_Unsupported_Type, type)));
+ }
+ try {
+ DocumentBuilder docBuilder = PDEXmlProcessorFactory.createDocumentBuilderWithErrorOnDOCTYPE();
+ Document document = docBuilder
+ .parse(new ByteArrayInputStream(serializedXML.getBytes(StandardCharsets.UTF_8)));
+ Element location = document.getDocumentElement();
+ NodeList childNodes = location.getChildNodes();
+ List requirements = IntStream.range(0, childNodes.getLength()).mapToObj(childNodes::item)
+ .filter(Element.class::isInstance).map(Element.class::cast)
+ .filter(element -> element.getNodeName()
+ .equalsIgnoreCase(RepositoryBundleContainer.ELEMENT_REQUIRE))
+ .flatMap(element -> {
+ String textContent = element.getTextContent();
+ Parameters parameters = new Parameters(textContent);
+ return CapReqBuilder.getRequirementsFrom(parameters).stream();
+ }).toList();
+ return new RepositoryBundleContainer(
+ location.getAttribute(RepositoryBundleContainer.ATTRIBUTE_URI), requirements);
+ } catch (Exception e) {
+ throw new CoreException(
+ Status.error(NLS.bind(Messages.TargetRefrenceLocationFactory_Parsing_Failed, e.getMessage()), e));
+ }
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF b/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
index f7693ec2fe..62c8423a3d 100644
--- a/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
+++ b/ui/org.eclipse.pde.ui/META-INF/MANIFEST.MF
@@ -121,7 +121,9 @@ Require-Bundle:
biz.aQute.bndlib;bundle-version="6.3.1"
Import-Package: org.eclipse.jdt.debug.ui.console,
org.eclipse.ui.internal.genericeditor,
- org.osgi.service.event;version="[1.4,2.0.0)"
+ org.osgi.service.event;version="[1.4,2.0.0)",
+ org.osgi.service.repository;version="1.1.0",
+ org.osgi.util.promise;version="1.3.0"
Bundle-RequiredExecutionEnvironment: JavaSE-17
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: org.eclipse.pde.ui
diff --git a/ui/org.eclipse.pde.ui/icons/dview16/memory_view.png b/ui/org.eclipse.pde.ui/icons/dview16/memory_view.png
new file mode 100644
index 0000000000..0e09f99274
Binary files /dev/null and b/ui/org.eclipse.pde.ui/icons/dview16/memory_view.png differ
diff --git a/ui/org.eclipse.pde.ui/icons/dview16/memory_view@2x.png b/ui/org.eclipse.pde.ui/icons/dview16/memory_view@2x.png
new file mode 100644
index 0000000000..28b9586940
Binary files /dev/null and b/ui/org.eclipse.pde.ui/icons/dview16/memory_view@2x.png differ
diff --git a/ui/org.eclipse.pde.ui/plugin.properties b/ui/org.eclipse.pde.ui/plugin.properties
index 68fe821945..f2465f6d2e 100644
--- a/ui/org.eclipse.pde.ui/plugin.properties
+++ b/ui/org.eclipse.pde.ui/plugin.properties
@@ -301,6 +301,8 @@ projectConfigurator.label.bundle = Eclipse Plugin/OSGi Bundle
projectConfigurator.label.feature = Eclipse Feature
create.module.info.label.pde= Create module-info.java
+locationProvider.reference.name = Target File
+locationProvider.reference.description = Add a reference to another target file
-locationProvider.description = Add a reference to another target file
-locationProvider.name = Target File
+locationProvider.repository.name = OSGi Repository
+locationProvider.repository.description = Add content from an OSGi Repository according to the Repository Service Specification
diff --git a/ui/org.eclipse.pde.ui/plugin.xml b/ui/org.eclipse.pde.ui/plugin.xml
index 0e07dae58d..a30d3ad863 100644
--- a/ui/org.eclipse.pde.ui/plugin.xml
+++ b/ui/org.eclipse.pde.ui/plugin.xml
@@ -2230,6 +2230,32 @@
type="org.eclipse.jface.viewers.ITreeContentProvider">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -2246,9 +2272,18 @@
class="org.eclipse.pde.internal.ui.shared.target.TargetReferenceLocationWizard"
icon="icons/obj16/target_profile_xml_obj.png"
id="org.eclipse.pde.ui.TargetReferenceProvisioner"
- name="%locationProvider.name">
+ name="%locationProvider.reference.name">
+
+ %locationProvider.reference.description
+
+
+
- %locationProvider.description
+ %locationProvider.repository.description
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
index b9d12d6edc..4430ef44e8 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPlugin.java
@@ -28,6 +28,7 @@
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.pde.internal.core.PDEPreferencesManager;
import org.eclipse.pde.internal.ui.launcher.PDELogFileProvider;
+import org.eclipse.pde.internal.ui.shared.target.RepositoryBundleContainerAdapterFactory;
import org.eclipse.pde.internal.ui.shared.target.TargetReferenceBundleContainerAdapterFactory;
import org.eclipse.pde.internal.ui.shared.target.TargetStatus;
import org.eclipse.pde.internal.ui.util.SWTUtil;
@@ -233,6 +234,7 @@ public void stop(BundleContext context) throws Exception {
Utilities.shutdown();
super.stop(context);
TargetReferenceBundleContainerAdapterFactory.LABEL_PROVIDER.dispose();
+ RepositoryBundleContainerAdapterFactory.LABEL_PROVIDER.dispose();
}
public PDELabelProvider getLabelProvider() {
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java
index 289bf3aabc..265f889b23 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/PDEPluginImages.java
@@ -42,6 +42,7 @@ public class PDEPluginImages {
private static final String PATH_OBJ = ICONS_PATH + "obj16/"; //$NON-NLS-1$
private static final String PATH_VIEW = ICONS_PATH + "view16/"; //$NON-NLS-1$
+ private static final String PATH_DVIEW = ICONS_PATH + "dview16/"; //$NON-NLS-1$
private static final String PATH_LCL = ICONS_PATH + "elcl16/"; //$NON-NLS-1$
private static final String PATH_LCL_DISABLED = ICONS_PATH + "dlcl16/"; //$NON-NLS-1$
private static final String PATH_TOOL = ICONS_PATH + "etool16/"; //$NON-NLS-1$
@@ -316,6 +317,7 @@ public class PDEPluginImages {
* View
*/
public static final ImageDescriptor DESC_ARGUMENT_TAB = create(PATH_VIEW, "variable_tab.png"); //$NON-NLS-1$
+ public static final ImageDescriptor DESC_TARGET_REPO = create(PATH_DVIEW, "memory_view.png"); //$NON-NLS-1$
private static ImageDescriptor create(String prefix, String name) {
return ImageDescriptor.createFromURL(makeImageURL(prefix, name));
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditRepositoryContainerPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditRepositoryContainerPage.java
new file mode 100644
index 0000000000..667d4d516a
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/EditRepositoryContainerPage.java
@@ -0,0 +1,290 @@
+/*******************************************************************************
+ * Copyright (c) 2009, 2023 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 577184 - [target] Allow references to other targets inside a target-file
+ * Christoph Läubrich - add support for repositories
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.shared.target;
+
+import static org.eclipse.swt.events.SelectionListener.widgetSelectedAdapter;
+
+import java.io.File;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.debug.ui.StringVariableSelectionDialog;
+import org.eclipse.jface.dialogs.IDialogSettings;
+import org.eclipse.jface.dialogs.IMessageProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.osgi.util.NLS;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.internal.core.target.RemoteTargetHandle;
+import org.eclipse.pde.internal.core.target.RepositoryBundleContainer;
+import org.eclipse.pde.internal.ui.PDEPlugin;
+import org.eclipse.pde.internal.ui.SWTFactory;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Combo;
+import org.eclipse.swt.widgets.Composite;
+
+/**
+ * Wizard page for creating a new directory bundle container.
+ *
+ * @see AddBundleContainerWizard
+ * @see AddBundleContainerSelectionPage
+ * @see ITargetLocation
+ */
+public class EditRepositoryContainerPage extends WizardPage implements IEditBundleContainerPage {
+
+ /**
+ * How long to wait before validating the directory
+ */
+ protected static final int TYPING_DELAY = 200;
+
+ protected Combo furiLocation;
+ protected RepositoryBundleContainer fContainer;
+
+ /**
+ * Dialog settings key for the most recent location
+ */
+ private static final String SETTINGS_LOCATION_1 = "location1"; //$NON-NLS-1$
+
+ /**
+ * Dialog settings key for the second most recent location
+ */
+ private static final String SETTINGS_LOCATION_2 = "location2"; //$NON-NLS-1$
+
+ /**
+ * Dialog settings key for the third most recent location
+ */
+ private static final String SETTINGS_LOCATION_3 = "location3"; //$NON-NLS-1$
+
+ public EditRepositoryContainerPage(RepositoryBundleContainer bundleContainer) {
+ super("EditRepositoryContainerPage"); //$NON-NLS-1$
+ fContainer = bundleContainer;
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+ setMessage(getDefaultMessage());
+ setTitle(getDefaultTitle());
+ setPageComplete(false);
+ Composite comp = SWTFactory.createComposite(parent, 1, 1, GridData.FILL_BOTH, 0, 0);
+ createLocationArea(comp);
+ setControl(comp);
+ initializeInputFields(fContainer);
+ }
+
+ /**
+ * @return the default title for this wizard page
+ */
+ protected String getDefaultTitle() {
+ if (fContainer == null) {
+ return Messages.EditRepositoryContainerPage_Add_Title;
+ }
+ return Messages.EditRepositoryContainerPage_Edit_Title;
+ }
+
+ /**
+ * @return the default message for this wizard page
+ */
+ protected String getDefaultMessage() {
+ return Messages.EditRepositoryContainerPage_Message;
+ }
+
+ /**
+ * Creates the area at the top of the page. Contains an entry form for a
+ * location path. This method may be overridden by subclasses to provide
+ * custom widgets
+ *
+ * @param parent
+ * parent composite
+ */
+ protected void createLocationArea(Composite parent) {
+ Composite locationComp = SWTFactory.createComposite(parent, 2, 1, GridData.FILL_HORIZONTAL, 0, 0);
+
+ SWTFactory.createLabel(locationComp, Messages.AddDirectoryContainerPage_2, 1);
+
+ furiLocation = SWTFactory.createCombo(locationComp, SWT.BORDER, 1, getLocationComboItems());
+ furiLocation.addModifyListener(e -> {
+ setPageComplete(validateInput());
+ });
+ try {
+ String location = fContainer != null ? fContainer.getLocation(false) : ""; //$NON-NLS-1$
+ furiLocation.setText(location);
+ } catch (CoreException e) {
+ setErrorMessage(e.getMessage());
+ }
+
+ Composite buttonComp = SWTFactory.createComposite(locationComp, 2, 2, GridData.CENTER, 0, 0);
+ GridData gd = (GridData) buttonComp.getLayoutData();
+ gd.horizontalAlignment = SWT.RIGHT;
+
+ Button variablesButton = SWTFactory.createPushButton(buttonComp, Messages.EditDirectoryContainerPage_1, null);
+ variablesButton.addSelectionListener(widgetSelectedAdapter(e -> {
+ StringVariableSelectionDialog dialog = new StringVariableSelectionDialog(getShell());
+ dialog.open();
+ String variable = dialog.getVariableExpression();
+ if (variable != null) {
+ furiLocation.setText(furiLocation.getText() + variable);
+ }
+ }));
+ }
+
+ /**
+ * Initializes the fields use to describe the container. They should be
+ * filled in using the given container or set to default values if the
+ * container is null
.
+ *
+ * @param container
+ * bundle container being edited, possibly null
+ */
+ protected void initializeInputFields(ITargetLocation container) {
+ try {
+ String currentLocation = fContainer != null ? fContainer.getLocation(false) : ""; //$NON-NLS-1$
+ boolean found = false;
+ String[] items = furiLocation.getItems();
+ for (String item : items) {
+ if (item.equals(currentLocation)) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ furiLocation.add(currentLocation);
+ }
+ furiLocation.setText(currentLocation);
+
+ setPageComplete(validateInput());
+ } catch (CoreException e) {
+ PDEPlugin.log(e);
+ }
+ }
+
+ @Override
+ public boolean isPageComplete() {
+ return !furiLocation.getText().isBlank();
+ }
+
+ /**
+ * @return a list of previous locations from settings plus the default
+ * location
+ */
+ private String[] getLocationComboItems() {
+ List previousLocations = new ArrayList<>(4);
+ IDialogSettings settings = getDialogSettings();
+ if (settings != null) {
+ String location = settings.get(SETTINGS_LOCATION_1);
+ if (location != null) {
+ previousLocations.add(location);
+ }
+ location = settings.get(SETTINGS_LOCATION_2);
+ if (location != null) {
+ previousLocations.add(location);
+ }
+ location = settings.get(SETTINGS_LOCATION_3);
+ if (location != null) {
+ previousLocations.add(location);
+ }
+ }
+ return previousLocations.toArray(new String[previousLocations.size()]);
+ }
+
+ @Override
+ public void storeSettings() {
+ String newLocation = furiLocation.getText().trim();
+
+ int length = newLocation.length();
+ if (length > 0 && newLocation.charAt(length - 1) == File.separatorChar) {
+ newLocation = newLocation.substring(0, length - 1);
+ }
+ String[] items = furiLocation.getItems();
+ for (String item : items) {
+ if (item.equals(newLocation)) {
+ // Already have this location stored
+ return;
+ }
+ }
+ IDialogSettings settings = getDialogSettings();
+ if (settings != null) {
+ String location = settings.get(SETTINGS_LOCATION_2);
+ if (location != null) {
+ settings.put(SETTINGS_LOCATION_3, location);
+ }
+ location = settings.get(SETTINGS_LOCATION_1);
+ if (location != null) {
+ settings.put(SETTINGS_LOCATION_2, location);
+ }
+ settings.put(SETTINGS_LOCATION_1, newLocation);
+ }
+ }
+
+ @Override
+ public RepositoryBundleContainer getBundleContainer() {
+ String rawUri = furiLocation != null & !furiLocation.isDisposed() ? furiLocation.getText().trim() : ""; //$NON-NLS-1$
+ return new RepositoryBundleContainer(rawUri, fContainer == null ? List.of() : fContainer.getRequirements());
+ }
+
+ /**
+ * Validate the input fields before a container is created/edited. The
+ * page's enablement, message and completion should be updated.
+ *
+ * @return whether the finish button should be enabled and container
+ * creation should continue
+ */
+ protected boolean validateInput() {
+ if (furiLocation.isDisposed())
+ return false;
+
+ // Check if the text field is blank
+ if (furiLocation.getText().trim().length() == 0) {
+ setMessage(getDefaultMessage());
+ return false;
+ }
+
+ // Resolve any variables
+ URI location;
+ try {
+ location = RemoteTargetHandle.getEffectiveUri(furiLocation.getText().trim());
+ } catch (CoreException e) {
+ setMessage(e.getMessage(), IMessageProvider.WARNING);
+ return true;
+ } catch (URISyntaxException e) {
+ setMessage(e.getMessage(), IMessageProvider.ERROR);
+ return false;
+ }
+ try {
+ // and be converted to an URL
+ URL url = location.toURL();
+ if ("file".equalsIgnoreCase(url.getProtocol())) { //$NON-NLS-1$
+ File file = new File(location);
+ if (!file.isFile()) {
+ setMessage(NLS.bind(Messages.EditTargetContainerPage_Not_A_File, file.getAbsolutePath()),
+ IMessageProvider.ERROR);
+ return true;
+ }
+ }
+ } catch (MalformedURLException | RuntimeException e) {
+ setMessage(e.getMessage(), IMessageProvider.ERROR);
+ return false;
+ }
+ setMessage(getDefaultMessage());
+ return true;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
index 7e44530752..68fe25980e 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/Messages.java
@@ -152,6 +152,11 @@ public class Messages extends NLS {
public static String EditTargetContainerPage_Edit_Title;
public static String EditTargetContainerPage_Message;
public static String EditTargetContainerPage_Not_A_File;
+ public static String EditRepositoryContainerPage_Message;
+ public static String EditRepositoryContainerPage_Add_Title;
+ public static String EditRepositoryContainerPage_Edit_Title;
+ public static String SelectRepositoryContentPage_Title;
+ public static String SelectRepositoryContentPage_Description;
static {
// initialize resource bundle
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryBundleContainerAdapterFactory.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryBundleContainerAdapterFactory.java
new file mode 100644
index 0000000000..46e804f28e
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryBundleContainerAdapterFactory.java
@@ -0,0 +1,212 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.shared.target;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IAdapterFactory;
+import org.eclipse.core.runtime.IProgressMonitor;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ITreeContentProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.viewers.TreePath;
+import org.eclipse.jface.wizard.IWizard;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.internal.core.target.RepositoryBundleContainer;
+import org.eclipse.pde.internal.launching.IPDEConstants;
+import org.eclipse.pde.internal.ui.PDEPluginImages;
+import org.eclipse.pde.ui.target.ITargetLocationHandler;
+import org.eclipse.swt.graphics.Image;
+import org.osgi.resource.Requirement;
+
+public class RepositoryBundleContainerAdapterFactory implements IAdapterFactory {
+
+ private static final Object[] EMPTY_OBJECTS = new Object[0];
+
+ public static final ILabelProvider LABEL_PROVIDER = new LabelProvider() {
+
+ private Image repositoryImage;
+ private Image requirementImage;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof RepositoryBundleContainer container) {
+ try {
+ return container.getLocation(true);
+ } catch (CoreException e) {
+ return container.getUri();
+ }
+ }
+ if (element instanceof RequirementNode requirement) {
+ return requirement.requirement().toString();
+ }
+ return null;
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof RepositoryBundleContainer) {
+ if (repositoryImage == null) {
+ repositoryImage = PDEPluginImages.DESC_TARGET_REPO.createImage();
+ }
+ return repositoryImage;
+ }
+ if (element instanceof RequirementNode) {
+ if (requirementImage == null) {
+ requirementImage = PDEPluginImages.DESC_FILTER.createImage();
+ }
+ return requirementImage;
+ }
+ return null;
+ }
+
+ @Override
+ public void dispose() {
+ super.dispose();
+ if (repositoryImage != null) {
+ repositoryImage.dispose();
+ repositoryImage = null;
+ }
+ }
+ };
+
+ private static final ITargetLocationHandler LOCATION_HANDLER = new ITargetLocationHandler() {
+
+ @Override
+ public IStatus reload(ITargetDefinition target, ITargetLocation[] targetLocations, IProgressMonitor monitor) {
+ for (ITargetLocation location : targetLocations) {
+ if (location instanceof RepositoryBundleContainer container) {
+ container.reload();
+ }
+ }
+ return Status.OK_STATUS;
+ }
+
+ @Override
+ public IWizard getEditWizard(ITargetDefinition target, TreePath treePath) {
+ Object segment = treePath.getLastSegment();
+ if (segment instanceof RequirementNode node) {
+ // TODO maybe we can support a requirements editor?
+ segment = node.container;
+ }
+ if (segment instanceof RepositoryBundleContainer container) {
+ RepositoryLocationWizard wizard = new RepositoryLocationWizard();
+ wizard.setTarget(target);
+ wizard.setBundleContainer(container);
+ return wizard;
+ }
+ return null;
+ }
+
+ @Override
+ public boolean canEdit(ITargetDefinition target, TreePath treePath) {
+ Object segment = treePath.getLastSegment();
+ return segment instanceof RepositoryBundleContainer || segment instanceof RequirementNode;
+ }
+
+ public boolean canRemove(ITargetDefinition target, TreePath treePath) {
+ return treePath.getLastSegment() instanceof RequirementNode;
+ }
+
+ public IStatus remove(ITargetDefinition target, TreePath[] treePaths) {
+ boolean reload = false;
+ for (TreePath path : treePaths) {
+ Object lastSegment = path.getLastSegment();
+ if (lastSegment instanceof RequirementNode node) {
+ RepositoryBundleContainer container = node.container;
+ RepositoryBundleContainer newContainer = new RepositoryBundleContainer(container.getUri(),
+ container.getRequirements().stream().filter(req -> req != node.requirement()).toList());
+ ITargetLocation[] targetLocations = target.getTargetLocations();
+ for (int i = 0; i < targetLocations.length; i++) {
+ ITargetLocation loc = targetLocations[i];
+ if (loc == container) {
+ targetLocations[i] = newContainer;
+ }
+
+ }
+ }
+ }
+ return reload
+ ? new Status(IStatus.OK, IPDEConstants.UI_PLUGIN_ID, ITargetLocationHandler.STATUS_FORCE_RELOAD,
+ "reloaded", null) //$NON-NLS-1$
+ : Status.OK_STATUS;
+ }
+ };
+
+ private static final ITreeContentProvider TREE_CONTENT_PROVIDER = new ITreeContentProvider() {
+
+ @Override
+ public boolean hasChildren(Object element) {
+ if (element instanceof RepositoryBundleContainer container) {
+ return !container.getRequirements().isEmpty();
+ }
+ return false;
+ }
+
+ @Override
+ public Object getParent(Object element) {
+ if (element instanceof RequirementNode node) {
+ return node.container;
+ }
+ return null;
+ }
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ return EMPTY_OBJECTS; // will never be called...
+ }
+
+ @Override
+ public Object[] getChildren(Object parentElement) {
+ if (parentElement instanceof RepositoryBundleContainer container) {
+ return container.getRequirements().stream().map(req -> new RequirementNode(req, container)).toArray();
+ }
+ return EMPTY_OBJECTS;
+ }
+ };
+
+ @Override
+ public T getAdapter(Object adaptableObject, Class adapterType) {
+ if (adaptableObject instanceof RepositoryBundleContainer
+ || adaptableObject instanceof RequirementNode) {
+ if (adapterType == ILabelProvider.class) {
+ return adapterType.cast(LABEL_PROVIDER);
+ }
+ if (adapterType == ITargetLocationHandler.class) {
+ return adapterType.cast(LOCATION_HANDLER);
+ }
+ if (adapterType == ITreeContentProvider.class) {
+ return adapterType.cast(TREE_CONTENT_PROVIDER);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public Class>[] getAdapterList() {
+ return new Class>[] { ILabelProvider.class, ITargetLocationHandler.class, ITreeContentProvider.class };
+ }
+
+ /**
+ * Simple wrapper class to identify it uniquly when adaption take place and
+ * to record the parent
+ */
+ public static final record RequirementNode(Requirement requirement, RepositoryBundleContainer container) {
+
+ }
+
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryLocationWizard.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryLocationWizard.java
new file mode 100644
index 0000000000..65018a8083
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/RepositoryLocationWizard.java
@@ -0,0 +1,75 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.shared.target;
+
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.pde.core.target.ITargetDefinition;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.internal.core.target.RepositoryBundleContainer;
+import org.eclipse.pde.ui.target.ITargetLocationWizard;
+
+public class RepositoryLocationWizard extends Wizard implements ITargetLocationWizard {
+
+ private EditRepositoryContainerPage repositoryPage;
+ private ITargetLocation wizardLocation;
+ private ITargetDefinition target;
+ private RepositoryBundleContainer bundleContainer;
+ private SelectRepositoryContentPage selectionPage;
+
+ public RepositoryLocationWizard() {
+ setNeedsProgressMonitor(true);
+ }
+
+ @Override
+ public void setTarget(ITargetDefinition target) {
+ this.target = target;
+ }
+
+ @Override
+ public void addPages() {
+ addPage(repositoryPage = new EditRepositoryContainerPage(bundleContainer));
+ addPage(selectionPage = new SelectRepositoryContentPage(repositoryPage));
+
+ setWindowTitle(repositoryPage.getDefaultTitle());
+ }
+
+ @Override
+ public ITargetLocation[] getLocations() {
+ if (wizardLocation == null) {
+ return new ITargetLocation[0];
+ }
+ return new ITargetLocation[] { wizardLocation };
+ }
+
+ @Override
+ public boolean performFinish() {
+ wizardLocation = selectionPage.getBundleContainer();
+ if (target != null && bundleContainer != null) {
+ ITargetLocation[] locations = target.getTargetLocations();
+ for (int i = 0; i < locations.length; i++) {
+ ITargetLocation location = locations[i];
+ if (location == bundleContainer) {
+ locations[i] = wizardLocation;
+ }
+ }
+ target.setTargetLocations(locations);
+ }
+ return true;
+ }
+
+ public void setBundleContainer(RepositoryBundleContainer bundleContainer) {
+ this.bundleContainer = bundleContainer;
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/SelectRepositoryContentPage.java b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/SelectRepositoryContentPage.java
new file mode 100644
index 0000000000..c1646697a0
--- /dev/null
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/SelectRepositoryContentPage.java
@@ -0,0 +1,192 @@
+/*******************************************************************************
+ * Copyright (c) 2023 Christoph Läubrich 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:
+ * Christoph Läubrich - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.pde.internal.ui.shared.target;
+
+import java.lang.reflect.InvocationTargetException;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.jface.viewers.ArrayContentProvider;
+import org.eclipse.jface.viewers.CheckStateChangedEvent;
+import org.eclipse.jface.viewers.CheckboxTableViewer;
+import org.eclipse.jface.viewers.ICheckStateListener;
+import org.eclipse.jface.viewers.ICheckStateProvider;
+import org.eclipse.jface.viewers.LabelProvider;
+import org.eclipse.jface.wizard.WizardPage;
+import org.eclipse.pde.core.target.ITargetLocation;
+import org.eclipse.pde.internal.core.target.RepositoryBundleContainer;
+import org.eclipse.pde.internal.ui.PDEPluginImages;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Image;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.resource.Capability;
+import org.osgi.resource.Requirement;
+import org.osgi.resource.Resource;
+
+import aQute.bnd.osgi.repository.ResourcesRepository;
+import aQute.bnd.osgi.resource.CapReqBuilder;
+import aQute.bnd.osgi.resource.ResourceUtils;
+import aQute.bnd.osgi.resource.ResourceUtils.IdentityCapability;
+
+public class SelectRepositoryContentPage extends WizardPage implements IEditBundleContainerPage {
+
+ private EditRepositoryContainerPage repositoryPage;
+ private String lastLocation;
+ private CheckboxTableViewer viewer;
+ private Collection requirements;
+ private Set selected = ConcurrentHashMap.newKeySet();
+
+ protected SelectRepositoryContentPage(EditRepositoryContainerPage repositoryPage) {
+ super("SelectRepositoryContentPage"); //$NON-NLS-1$
+ this.repositoryPage = repositoryPage;
+ setTitle(Messages.SelectRepositoryContentPage_Title);
+ setDescription(Messages.SelectRepositoryContentPage_Description);
+ }
+
+ @Override
+ public void setVisible(boolean visible) {
+ if (visible) {
+ try {
+ RepositoryBundleContainer container = repositoryPage.getBundleContainer();
+ if (requirements == null) {
+ // only init the requirements once...
+ requirements = new HashSet<>(container.getRequirements());
+ }
+ String location = container.getLocation(true);
+ if (lastLocation != location) {
+ // Load the repository!!
+ getContainer().run(true, true, monitor -> {
+ try {
+ ResourcesRepository repository = container.getRepository(monitor);
+ selected.clear();
+ List resources = repository.getResources();
+ repository.findProviders(requirements).values().stream().flatMap(Collection::stream)
+ .map(Capability::getResource).distinct().forEach(selected::add);
+ if (viewer != null) {
+ viewer.getControl().getDisplay().execute(() -> {
+ if (viewer.getControl().isDisposed()) {
+ return;
+ }
+ viewer.setInput(resources);
+ });
+ }
+ } catch (CoreException e) {
+ throw new InvocationTargetException(e);
+ }
+ });
+ }
+ setErrorMessage(null);
+ } catch (CoreException e) {
+ setErrorMessage(e.getStatus().getMessage());
+ } catch (InvocationTargetException e) {
+ setErrorMessage(e.getMessage());
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ super.setVisible(visible);
+ }
+
+ @Override
+ public void createControl(Composite parent) {
+
+ viewer = CheckboxTableViewer.newCheckList(parent, SWT.NONE);
+ viewer.setCheckStateProvider(new ICheckStateProvider() {
+
+ @Override
+ public boolean isGrayed(Object element) {
+ return false;
+ }
+
+ @Override
+ public boolean isChecked(Object element) {
+ if (element instanceof Resource resource) {
+ return selected.contains(resource);
+ }
+ return false;
+ }
+ });
+ viewer.setContentProvider(ArrayContentProvider.getInstance());
+ viewer.setLabelProvider(new LabelProvider() {
+ private Image pluginImage;
+
+ @Override
+ public String getText(Object element) {
+ if (element instanceof Resource resource) {
+ return resource.toString();
+ }
+ return ""; //$NON-NLS-1$
+ }
+
+ @Override
+ public Image getImage(Object element) {
+ if (element instanceof Resource resource) {
+ if (ResourceUtils.getBundleCapability(resource) != null) {
+ if (pluginImage == null) {
+ pluginImage = PDEPluginImages.DESC_PLUGIN_OBJ.createImage();
+ viewer.getControl().addDisposeListener(e -> pluginImage.dispose());
+ }
+ return pluginImage;
+ }
+ }
+ return null;
+ }
+ });
+ viewer.addCheckStateListener(new ICheckStateListener() {
+
+ @Override
+ public void checkStateChanged(CheckStateChangedEvent event) {
+ Object element = event.getElement();
+ if (element instanceof Resource resource) {
+ Requirement requirement = getRequirement(resource);
+ if (requirement != null) {
+ if (event.getChecked()) {
+ selected.add(resource);
+ requirements.add(requirement);
+ } else {
+ selected.remove(resource);
+ requirements.remove(requirement);
+ }
+ }
+ }
+ }
+ });
+ setControl(viewer.getControl());
+ }
+
+ protected Requirement getRequirement(Resource resource) {
+ IdentityCapability identity = ResourceUtils.getIdentityCapability(resource);
+ String v = identity.version().toString();
+ return CapReqBuilder.createSimpleRequirement(identity.getNamespace(), identity.osgi_identity(),
+ String.format("[%s,%s]", v, v)) //$NON-NLS-1$
+ .setResource(resource).buildRequirement();
+ }
+
+ @Override
+ public ITargetLocation getBundleContainer() {
+ RepositoryBundleContainer container = repositoryPage.getBundleContainer();
+ return new RepositoryBundleContainer(container.getUri(),
+ requirements == null ? container.getRequirements() : List.copyOf(requirements));
+ }
+
+ @Override
+ public void storeSettings() {
+
+ }
+
+}
diff --git a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
index 83a8650ae7..84a8f4dee4 100644
--- a/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
+++ b/ui/org.eclipse.pde.ui/src/org/eclipse/pde/internal/ui/shared/target/messages.properties
@@ -143,5 +143,10 @@ UpdateTargetJob_UpdateJobName=Update Target Definition
UpdateTargetJob_UpdatingTarget=Updating Target
EditTargetContainerPage_Add_Title=Create target reference
EditTargetContainerPage_Edit_Title=Edit target reference
-EditTargetContainerPage_Message=Please enter an URI to the target to be referenced
-EditTargetContainerPage_Not_A_File={0} is not a file
\ No newline at end of file
+EditTargetContainerPage_Message=Please enter a URI to the target to be referenced
+EditTargetContainerPage_Not_A_File={0} is not a file
+EditRepositoryContainerPage_Message=Please enter a URI that points to the repository
+EditRepositoryContainerPage_Add_Title=Create OSGi Repository Location
+EditRepositoryContainerPage_Edit_Title=Edit OSGi Repository Location
+SelectRepositoryContentPage_Title=Select Content
+SelectRepositoryContentPage_Description=Select the content that should be included in this location
\ No newline at end of file