From d265d7e3095b3daedc214e5dfe1cc7d7217fd803 Mon Sep 17 00:00:00 2001
From: Lars Vogel
Date: Mon, 7 Sep 2026 09:56:52 +0200
Subject: [PATCH] Provide bundle groups without scanning the installation
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The only IBundleGroupProvider in the SDK was BundleGroupComponent in
org.eclipse.update.configurator. Asking it for bundle groups built the
legacy PlatformConfiguration, which reconciles the install site and opens
every plugins/*.jar to read its manifest, on the IDE startup path and on
the UI thread, because WorkbenchActionBuilder needs feature information to
decide whether to contribute the Welcome and Tips and Tricks actions.
The new bundle org.eclipse.core.runtime.bundlegroups registers an
equivalent provider that reads only feature data: the feature list from the
platform.xml p2 writes at provisioning time, with a scan of the features
directory as a fallback, branding from the about.ini of the branding
bundle, and description and bundle list from feature.xml on first access.
No plug-in jar is opened at any point. Its platform.xml model and parser
are copied from the read path of org.eclipse.equinox.internal.p2.update
and pruned; a copy rather than a dependency keeps p2 out of
org.eclipse.rcp, which ships without it.
BundleGroupComponent is removed in the same change, since two registered
providers would list every feature twice in the About dialog. With it gone
nothing reaches the branding reader in org.eclipse.update.configurator any
more, so that package and the IBundleGroup implementation of FeatureEntry
go as well. It was exported to org.eclipse.update.core only, which no
longer exists. FeatureEntryTests covered only the license URL that went
with it and is removed too. The deprecated IPlatformConfiguration API and
its factory stay untouched.
Assisted-by: multiple AI agents and layers of automated tooling 🤖
---
.../.classpath | 7 +
.../.gitignore | 1 +
.../.project | 34 +++
.../org.eclipse.core.resources.prefs | 2 +
.../.settings/org.eclipse.core.runtime.prefs | 2 +
.../.settings/org.eclipse.jdt.core.prefs | 126 +++++++++
.../.settings/org.eclipse.jdt.ui.prefs | 147 ++++++++++
.../org.eclipse.pde.ds.annotations.prefs | 7 +
.../META-INF/MANIFEST.MF | 16 ++
.../about.html | 36 +++
.../build.properties | 21 ++
.../plugin.properties | 15 +
.../runtime/bundlegroups}/AboutInfo.java | 2 +-
.../bundlegroups/FeatureBundleGroup.java | 262 ++++++++++++++++++
.../FeatureBundleGroupProvider.java | 170 ++++++++++++
.../runtime/bundlegroups/FeatureManifest.java | 32 +++
.../bundlegroups/FeatureXmlParser.java | 195 +++++++++++++
.../bundlegroups}/IBundleGroupConstants.java | 7 +-
.../bundlegroups}/IProductConstants.java | 2 +-
.../runtime/bundlegroups}/IniFileReader.java | 5 +-
.../runtime/bundlegroups/Messages.java | 37 +++
.../runtime/bundlegroups/messages.properties | 22 ++
.../platformxml/Configuration.java | 89 ++++++
.../platformxml/ConfigurationConstants.java | 32 +++
.../platformxml/ConfigurationParser.java | 227 +++++++++++++++
.../bundlegroups/platformxml/Feature.java | 66 +++++
.../bundlegroups/platformxml/Site.java | 48 ++++
.../tests/AutomatedConfiguratorSuite.java | 2 -
.../configurator/tests/FeatureEntryTests.java | 43 ---
.../META-INF/MANIFEST.MF | 6 +-
.../configurator/BundleGroupComponent.java | 64 -----
.../internal/configurator/FeatureEntry.java | 183 +-----------
.../configurator/FullFeatureParser.java | 30 --
.../internal/configurator/Messages.java | 6 -
.../update/internal/configurator/Utils.java | 92 ------
.../internal/configurator/messages.properties | 6 -
36 files changed, 1603 insertions(+), 439 deletions(-)
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.classpath
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.gitignore
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.project
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.resources.prefs
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.runtime.prefs
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.core.prefs
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.ui.prefs
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.pde.ds.annotations.prefs
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/META-INF/MANIFEST.MF
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/about.html
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/build.properties
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/plugin.properties
rename {update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding => runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups}/AboutInfo.java (99%)
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroup.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroupProvider.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureManifest.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureXmlParser.java
rename {update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding => runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups}/IBundleGroupConstants.java (86%)
rename {update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding => runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups}/IProductConstants.java (98%)
rename {update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding => runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups}/IniFileReader.java (98%)
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/Messages.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/messages.properties
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Configuration.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationConstants.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationParser.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Feature.java
create mode 100644 runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Site.java
delete mode 100644 update/org.eclipse.update.configurator.tests/src/org/eclipse/update/internal/configurator/tests/FeatureEntryTests.java
delete mode 100644 update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/BundleGroupComponent.java
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.classpath b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.classpath
new file mode 100644
index 00000000000..375961e4d61
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.classpath
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.gitignore b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.gitignore
new file mode 100644
index 00000000000..14435d8efa9
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.gitignore
@@ -0,0 +1 @@
+OSGI-INF/org.eclipse.*.xml
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.project b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.project
new file mode 100644
index 00000000000..c4b531be21f
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.project
@@ -0,0 +1,34 @@
+
+
+ org.eclipse.core.runtime.bundlegroups
+
+
+
+
+
+ org.eclipse.jdt.core.javabuilder
+
+
+
+
+ org.eclipse.pde.ManifestBuilder
+
+
+
+
+ org.eclipse.pde.SchemaBuilder
+
+
+
+
+ org.eclipse.pde.api.tools.apiAnalysisBuilder
+
+
+
+
+
+ org.eclipse.pde.PluginNature
+ org.eclipse.jdt.core.javanature
+ org.eclipse.pde.api.tools.apiAnalysisNature
+
+
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.resources.prefs b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.resources.prefs
new file mode 100644
index 00000000000..99f26c0203a
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.resources.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+encoding/=UTF-8
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.runtime.prefs b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.runtime.prefs
new file mode 100644
index 00000000000..5a0ad22d2a7
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.core.runtime.prefs
@@ -0,0 +1,2 @@
+eclipse.preferences.version=1
+line.separator=\n
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.core.prefs b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 00000000000..3a8fc308afb
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,126 @@
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled
+org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore
+org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull
+org.eclipse.jdt.core.compiler.annotation.nonnull.secondary=
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault
+org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable
+org.eclipse.jdt.core.compiler.annotation.nullable.secondary=
+org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.doc.comment.support=enabled
+org.eclipse.jdt.core.compiler.problem.APILeak=warning
+org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.autoboxing=ignore
+org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning
+org.eclipse.jdt.core.compiler.problem.deadCode=warning
+org.eclipse.jdt.core.compiler.problem.deprecation=warning
+org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled
+org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled
+org.eclipse.jdt.core.compiler.problem.discouragedReference=warning
+org.eclipse.jdt.core.compiler.problem.emptyStatement=warning
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=warning
+org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore
+org.eclipse.jdt.core.compiler.problem.fatalOptionalError=enabled
+org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore
+org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning
+org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning
+org.eclipse.jdt.core.compiler.problem.forbiddenReference=error
+org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning
+org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled
+org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning
+org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=ignore
+org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=warning
+org.eclipse.jdt.core.compiler.problem.invalidJavadoc=ignore
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTags=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsDeprecatedRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsNotVisibleRef=disabled
+org.eclipse.jdt.core.compiler.problem.invalidJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore
+org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning
+org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore
+org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled
+org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocComments=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocCommentsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingJavadocTags=ignore
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.missingJavadocTagsVisibility=public
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled
+org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning
+org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore
+org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning
+org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning
+org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore
+org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning
+org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning
+org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error
+org.eclipse.jdt.core.compiler.problem.nullReference=ignore
+org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error
+org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning
+org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning
+org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning
+org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore
+org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore
+org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore
+org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning
+org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore
+org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=warning
+org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=warning
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled
+org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning
+org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled
+org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled
+org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled
+org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore
+org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning
+org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning
+org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled
+org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning
+org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning
+org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore
+org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning
+org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled
+org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info
+org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore
+org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=warning
+org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled
+org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedImport=error
+org.eclipse.jdt.core.compiler.problem.unusedLabel=warning
+org.eclipse.jdt.core.compiler.problem.unusedLocal=error
+org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled
+org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled
+org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=error
+org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore
+org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning
+org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.ui.prefs b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.ui.prefs
new file mode 100644
index 00000000000..81f11511252
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.jdt.ui.prefs
@@ -0,0 +1,147 @@
+eclipse.preferences.version=1
+editor_save_participant_org.eclipse.jdt.ui.postsavelistener.cleanup=true
+org.eclipse.jdt.ui.ondemandthreshold=99
+org.eclipse.jdt.ui.staticondemandthreshold=99
+sp_cleanup.add_all=false
+sp_cleanup.add_default_serial_version_id=true
+sp_cleanup.add_generated_serial_version_id=false
+sp_cleanup.add_missing_annotations=true
+sp_cleanup.add_missing_deprecated_annotations=true
+sp_cleanup.add_missing_methods=false
+sp_cleanup.add_missing_nls_tags=false
+sp_cleanup.add_missing_override_annotations=true
+sp_cleanup.add_missing_override_annotations_interface_methods=true
+sp_cleanup.add_serial_version_id=false
+sp_cleanup.also_simplify_lambda=true
+sp_cleanup.always_use_blocks=true
+sp_cleanup.always_use_parentheses_in_expressions=false
+sp_cleanup.always_use_this_for_non_static_field_access=false
+sp_cleanup.always_use_this_for_non_static_method_access=false
+sp_cleanup.array_with_curly=false
+sp_cleanup.arrays_fill=false
+sp_cleanup.bitwise_conditional_expression=false
+sp_cleanup.boolean_literal=false
+sp_cleanup.boolean_value_rather_than_comparison=true
+sp_cleanup.break_loop=false
+sp_cleanup.collection_cloning=false
+sp_cleanup.comparing_on_criteria=false
+sp_cleanup.comparison_statement=false
+sp_cleanup.controlflow_merge=false
+sp_cleanup.convert_functional_interfaces=true
+sp_cleanup.convert_to_enhanced_for_loop=true
+sp_cleanup.convert_to_enhanced_for_loop_if_loop_var_used=true
+sp_cleanup.convert_to_switch_expressions=false
+sp_cleanup.correct_indentation=false
+sp_cleanup.do_while_rather_than_while=false
+sp_cleanup.double_negation=false
+sp_cleanup.else_if=false
+sp_cleanup.embedded_if=false
+sp_cleanup.evaluate_nullable=false
+sp_cleanup.extract_increment=false
+sp_cleanup.format_source_code=true
+sp_cleanup.format_source_code_changes_only=true
+sp_cleanup.hash=false
+sp_cleanup.if_condition=false
+sp_cleanup.insert_inferred_type_arguments=false
+sp_cleanup.instanceof=false
+sp_cleanup.instanceof_keyword=false
+sp_cleanup.invert_equals=false
+sp_cleanup.join=false
+sp_cleanup.lazy_logical_operator=false
+sp_cleanup.make_local_variable_final=true
+sp_cleanup.make_parameters_final=false
+sp_cleanup.make_private_fields_final=true
+sp_cleanup.make_type_abstract_if_missing_method=false
+sp_cleanup.make_variable_declarations_final=false
+sp_cleanup.map_cloning=false
+sp_cleanup.merge_conditional_blocks=false
+sp_cleanup.multi_catch=false
+sp_cleanup.never_use_blocks=false
+sp_cleanup.never_use_parentheses_in_expressions=true
+sp_cleanup.no_string_creation=false
+sp_cleanup.no_super=false
+sp_cleanup.number_suffix=false
+sp_cleanup.objects_equals=false
+sp_cleanup.on_save_use_additional_actions=true
+sp_cleanup.one_if_rather_than_duplicate_blocks_that_fall_through=false
+sp_cleanup.operand_factorization=false
+sp_cleanup.organize_imports=true
+sp_cleanup.overridden_assignment=false
+sp_cleanup.overridden_assignment_move_decl=true
+sp_cleanup.plain_replacement=false
+sp_cleanup.precompile_regex=false
+sp_cleanup.primitive_comparison=false
+sp_cleanup.primitive_parsing=false
+sp_cleanup.primitive_rather_than_wrapper=true
+sp_cleanup.primitive_serialization=false
+sp_cleanup.pull_out_if_from_if_else=false
+sp_cleanup.pull_up_assignment=false
+sp_cleanup.push_down_negation=false
+sp_cleanup.qualify_static_field_accesses_with_declaring_class=false
+sp_cleanup.qualify_static_member_accesses_through_instances_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_through_subtypes_with_declaring_class=true
+sp_cleanup.qualify_static_member_accesses_with_declaring_class=true
+sp_cleanup.qualify_static_method_accesses_with_declaring_class=false
+sp_cleanup.reduce_indentation=false
+sp_cleanup.redundant_comparator=false
+sp_cleanup.redundant_falling_through_block_end=false
+sp_cleanup.remove_private_constructors=true
+sp_cleanup.remove_redundant_modifiers=false
+sp_cleanup.remove_redundant_semicolons=true
+sp_cleanup.remove_redundant_type_arguments=true
+sp_cleanup.remove_trailing_whitespaces=true
+sp_cleanup.remove_trailing_whitespaces_all=true
+sp_cleanup.remove_trailing_whitespaces_ignore_empty=false
+sp_cleanup.remove_unnecessary_array_creation=false
+sp_cleanup.remove_unnecessary_casts=true
+sp_cleanup.remove_unnecessary_nls_tags=true
+sp_cleanup.remove_unused_imports=true
+sp_cleanup.remove_unused_local_variables=false
+sp_cleanup.remove_unused_method_parameters=false
+sp_cleanup.remove_unused_private_fields=true
+sp_cleanup.remove_unused_private_members=false
+sp_cleanup.remove_unused_private_methods=true
+sp_cleanup.remove_unused_private_types=true
+sp_cleanup.replace_deprecated_calls=false
+sp_cleanup.return_expression=false
+sp_cleanup.simplify_lambda_expression_and_method_ref=true
+sp_cleanup.single_used_field=false
+sp_cleanup.sort_members=false
+sp_cleanup.sort_members_all=false
+sp_cleanup.standard_comparison=false
+sp_cleanup.static_inner_class=false
+sp_cleanup.strictly_equal_or_different=false
+sp_cleanup.stringbuffer_to_stringbuilder=false
+sp_cleanup.stringbuilder=false
+sp_cleanup.stringbuilder_for_local_vars=true
+sp_cleanup.stringconcat_stringbuffer_stringbuilder=false
+sp_cleanup.stringconcat_to_textblock=false
+sp_cleanup.substring=false
+sp_cleanup.switch=false
+sp_cleanup.system_property=false
+sp_cleanup.system_property_boolean=false
+sp_cleanup.system_property_file_encoding=false
+sp_cleanup.system_property_file_separator=false
+sp_cleanup.system_property_line_separator=false
+sp_cleanup.system_property_path_separator=false
+sp_cleanup.ternary_operator=false
+sp_cleanup.try_with_resource=false
+sp_cleanup.unlooped_while=false
+sp_cleanup.unreachable_block=false
+sp_cleanup.use_anonymous_class_creation=false
+sp_cleanup.use_autoboxing=false
+sp_cleanup.use_blocks=false
+sp_cleanup.use_blocks_only_for_return_and_throw=false
+sp_cleanup.use_directly_map_method=false
+sp_cleanup.use_lambda=true
+sp_cleanup.use_parentheses_in_expressions=false
+sp_cleanup.use_string_is_blank=false
+sp_cleanup.use_this_for_non_static_field_access=false
+sp_cleanup.use_this_for_non_static_field_access_only_if_necessary=true
+sp_cleanup.use_this_for_non_static_method_access=false
+sp_cleanup.use_this_for_non_static_method_access_only_if_necessary=true
+sp_cleanup.use_unboxing=false
+sp_cleanup.use_var=false
+sp_cleanup.useless_continue=false
+sp_cleanup.useless_return=false
+sp_cleanup.valueof_rather_than_instantiation=false
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.pde.ds.annotations.prefs b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.pde.ds.annotations.prefs
new file mode 100644
index 00000000000..5faf08b7d5c
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/.settings/org.eclipse.pde.ds.annotations.prefs
@@ -0,0 +1,7 @@
+dsVersion=V1_4
+eclipse.preferences.version=1
+enabled=true
+generateBundleActivationPolicyLazy=true
+path=OSGI-INF
+validationErrorLevel=error
+validationErrorLevel.missingImplicitUnbindMethod=error
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/META-INF/MANIFEST.MF b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/META-INF/MANIFEST.MF
new file mode 100644
index 00000000000..ca423529e0a
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/META-INF/MANIFEST.MF
@@ -0,0 +1,16 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.core.runtime.bundlegroups;singleton:=true
+Bundle-Version: 1.0.0.qualifier
+Bundle-Vendor: %providerName
+Bundle-Localization: plugin
+Require-Bundle: org.eclipse.core.runtime;bundle-version="3.29.0"
+Bundle-RequiredExecutionEnvironment: JavaSE-21
+Import-Package: javax.xml.parsers,
+ org.w3c.dom,
+ org.xml.sax,
+ org.xml.sax.helpers
+Bundle-ActivationPolicy: lazy
+Automatic-Module-Name: org.eclipse.core.runtime.bundlegroups
+Service-Component: OSGI-INF/org.eclipse.core.internal.runtime.bundlegroups.FeatureBundleGroupProvider.xml
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/about.html b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/about.html
new file mode 100644
index 00000000000..164f781a8fd
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/about.html
@@ -0,0 +1,36 @@
+
+
+
+
+About
+
+
+ About This Content
+
+ November 30, 2017
+ License
+
+
+ The Eclipse Foundation makes available all content in this plug-in
+ ("Content"). Unless otherwise indicated below, the Content
+ is provided to you under the terms and conditions of the Eclipse
+ Public License Version 2.0 ("EPL"). A copy of the EPL is
+ available at http://www.eclipse.org/legal/epl-2.0.
+ For purposes of the EPL, "Program" will mean the Content.
+
+
+
+ If you did not receive this Content directly from the Eclipse
+ Foundation, the Content is being redistributed by another party
+ ("Redistributor") and different terms and conditions may
+ apply to your use of any object code in the Content. Check the
+ Redistributor's license that was provided with the Content. If no such
+ license exists, contact the Redistributor. Unless otherwise indicated
+ below, the terms and conditions of the EPL still apply to any source
+ code in the Content and such source code may be obtained at http://www.eclipse.org.
+
+
+
+
\ No newline at end of file
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/build.properties b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/build.properties
new file mode 100644
index 00000000000..9b2f9357689
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/build.properties
@@ -0,0 +1,21 @@
+###############################################################################
+# Copyright (c) 2026 Lars Vogel 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:
+# Lars Vogel - initial API and implementation
+###############################################################################
+bin.includes = META-INF/,\
+ .,\
+ about.html,\
+ plugin.properties,\
+ OSGI-INF/
+src.includes = about.html
+source.. = src/
+output.. = bin/
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/plugin.properties b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/plugin.properties
new file mode 100644
index 00000000000..756b8e0331e
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/plugin.properties
@@ -0,0 +1,15 @@
+###############################################################################
+# Copyright (c) 2026 Lars Vogel 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:
+# Lars Vogel - initial API and implementation
+###############################################################################
+pluginName= Installed Feature Bundle Groups
+providerName= Eclipse.org
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/AboutInfo.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/AboutInfo.java
similarity index 99%
rename from update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/AboutInfo.java
rename to runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/AboutInfo.java
index 5d3c412d7ef..0169c63b8ee 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/AboutInfo.java
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/AboutInfo.java
@@ -11,7 +11,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
-package org.eclipse.update.internal.configurator.branding;
+package org.eclipse.core.internal.runtime.bundlegroups;
import java.net.URL;
import java.util.Hashtable;
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroup.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroup.java
new file mode 100644
index 00000000000..bd8fcc7c985
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroup.java
@@ -0,0 +1,262 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Lars Vogel 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:
+ * Lars Vogel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups;
+
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.ResourceBundle;
+import java.util.StringTokenizer;
+
+import org.eclipse.core.runtime.IBundleGroup;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.Platform;
+import org.osgi.framework.Bundle;
+
+/**
+ * A bundle group backed by an installed feature. The feature identity comes
+ * from platform.xml, everything else is read from the feature and its branding
+ * bundle on demand.
+ */
+public class FeatureBundleGroup implements IBundleGroup, IBundleGroupConstants, IProductConstants {
+
+ private static final String FEATURE_XML = "feature.xml"; //$NON-NLS-1$
+ private static final String FEATURE_PROPERTIES = "feature"; //$NON-NLS-1$
+ private static final String KEY_PREFIX = "%"; //$NON-NLS-1$
+ private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$
+
+ private final String id;
+ private final String version;
+ private final String pluginIdentifier;
+ private final String pluginVersion;
+ private final URI featureLocation;
+
+ private AboutInfo branding;
+ private FeatureManifest manifest;
+ private boolean fullyParsed;
+ private ResourceBundle resourceBundle;
+ private boolean resourceBundleLoaded;
+
+ FeatureBundleGroup(String id, String version, String pluginIdentifier, String pluginVersion, URI featureLocation) {
+ this.id = id;
+ this.version = version;
+ this.pluginIdentifier = pluginIdentifier;
+ this.pluginVersion = pluginVersion;
+ this.featureLocation = featureLocation;
+ }
+
+ /**
+ * A feature contributes a bundle group only when its branding bundle is
+ * installed.
+ */
+ boolean hasBranding() {
+ String bundleId = getFeaturePluginIdentifier();
+ return bundleId != null && Platform.getBundle(bundleId) != null;
+ }
+
+ private String getFeaturePluginIdentifier() {
+ return pluginIdentifier != null && !pluginIdentifier.isEmpty() ? pluginIdentifier : id;
+ }
+
+ @Override
+ public String getIdentifier() {
+ return id;
+ }
+
+ @Override
+ public String getVersion() {
+ return version;
+ }
+
+ @Override
+ public String getName() {
+ return getBranding().getProductName();
+ }
+
+ @Override
+ public String getProviderName() {
+ return getBranding().getProviderName();
+ }
+
+ @Override
+ public String getDescription() {
+ FeatureManifest parsed = fullParse();
+ return parsed == null ? null : getResourceString(parsed.description);
+ }
+
+ @Override
+ public Bundle[] getBundles() {
+ FeatureManifest parsed = fullParse();
+ if (parsed == null) {
+ return new Bundle[0];
+ }
+ List bundles = new ArrayList<>(parsed.pluginIds.size());
+ for (String pluginId : parsed.pluginIds) {
+ Bundle bundle = Platform.getBundle(pluginId);
+ if (bundle != null) {
+ bundles.add(bundle);
+ }
+ }
+ return bundles.toArray(new Bundle[bundles.size()]);
+ }
+
+ @Override
+ public String getProperty(String key) {
+ if (key == null) {
+ return null;
+ }
+ AboutInfo info = getBranding();
+
+ // IBundleGroupConstants
+ if (key.equals(FEATURE_IMAGE)) {
+ return toExternalForm(info.getFeatureImageURL());
+ } else if (key.equals(TIPS_AND_TRICKS_HREF)) {
+ return info.getTipsAndTricksHref();
+ } else if (key.equals(IBundleGroupConstants.WELCOME_PAGE)) { // same value is used by product and bundle group
+ return toExternalForm(info.getWelcomePageURL());
+ } else if (key.equals(WELCOME_PERSPECTIVE)) {
+ return info.getWelcomePerspectiveId();
+ } else if (key.equals(BRANDING_BUNDLE_ID)) {
+ return pluginIdentifier;
+ } else if (key.equals(BRANDING_BUNDLE_VERSION)) {
+ return pluginVersion;
+ } else if (key.equals(APP_NAME)) {
+ return info.getAppName();
+ } else if (key.equals(ABOUT_TEXT)) {
+ return info.getAboutText();
+ } else if (key.equals(ABOUT_IMAGE)) {
+ return toExternalForm(info.getAboutImageURL());
+ } else if (key.equals(WINDOW_IMAGE)) {
+ return toExternalForm(info.getWindowImageURL());
+ } else if (key.equals(WINDOW_IMAGES)) {
+ URL[] urls = info.getWindowImagesURLs();
+ if (urls == null) {
+ return null;
+ }
+ StringBuilder windowImagesURLs = new StringBuilder();
+ for (int i = 0; i < urls.length; i++) {
+ windowImagesURLs.append(urls[i].toExternalForm());
+ if (i != urls.length - 1) {
+ windowImagesURLs.append(',');
+ }
+ }
+ return windowImagesURLs.toString();
+ } else if (key.equals(LICENSE_HREF)) {
+ return getLicenseURL();
+ }
+
+ return null;
+ }
+
+ private static String toExternalForm(URL url) {
+ return url == null ? null : url.toExternalForm();
+ }
+
+ private synchronized AboutInfo getBranding() {
+ if (branding == null) {
+ branding = AboutInfo.readFeatureInfo(id, version, getFeaturePluginIdentifier());
+ }
+ return branding;
+ }
+
+ private String getLicenseURL() {
+ FeatureManifest parsed = fullParse();
+ if (parsed == null || parsed.licenseURL == null) {
+ return null;
+ }
+ String resolved = getResourceString(parsed.licenseURL);
+ if (resolved.startsWith("http://") || resolved.startsWith("https://")) { //$NON-NLS-1$ //$NON-NLS-2$
+ return resolved;
+ }
+ return featureLocation.resolve(resolved).toString();
+ }
+
+ private synchronized FeatureManifest fullParse() {
+ if (!fullyParsed) {
+ fullyParsed = true;
+ try {
+ manifest = FeatureXmlParser.parse(featureLocation.resolve(FEATURE_XML).toURL(), false);
+ } catch (MalformedURLException | IllegalArgumentException e) {
+ ILog.of(FeatureBundleGroup.class).error("Cannot locate the feature.xml of " + id, e); //$NON-NLS-1$
+ }
+ }
+ return manifest;
+ }
+
+ /*
+ * Resolves a %key reference against the feature.properties of this feature.
+ */
+ private String getResourceString(String value) {
+ if (value == null) {
+ return null;
+ }
+ String s = value.trim();
+ if (s.isEmpty() || !s.startsWith(KEY_PREFIX)) {
+ return value;
+ }
+ if (s.startsWith(KEY_DOUBLE_PREFIX)) {
+ return s.substring(1);
+ }
+
+ int index = s.indexOf(' ');
+ String key = index == -1 ? s : s.substring(0, index);
+ String defaultValue = index == -1 ? s : s.substring(index + 1);
+
+ ResourceBundle bundle = getResourceBundle();
+ if (bundle == null) {
+ return defaultValue;
+ }
+ try {
+ return bundle.getString(key.substring(1));
+ } catch (MissingResourceException e) {
+ return defaultValue;
+ }
+ }
+
+ private synchronized ResourceBundle getResourceBundle() {
+ if (!resourceBundleLoaded) {
+ resourceBundleLoaded = true;
+ try {
+ ClassLoader loader = new URLClassLoader(new URL[] { featureLocation.toURL() }, null);
+ resourceBundle = ResourceBundle.getBundle(FEATURE_PROPERTIES, getDefaultLocale(), loader);
+ } catch (MissingResourceException | MalformedURLException | IllegalArgumentException e) {
+ // features are not required to be translated
+ }
+ }
+ return resourceBundle;
+ }
+
+ private static Locale getDefaultLocale() {
+ String nl = Platform.getNL();
+ if (nl == null) {
+ return Locale.getDefault();
+ }
+ StringTokenizer locales = new StringTokenizer(nl, "_"); //$NON-NLS-1$
+ switch (locales.countTokens()) {
+ case 1:
+ return Locale.of(locales.nextToken());
+ case 2:
+ return Locale.of(locales.nextToken(), locales.nextToken());
+ case 3:
+ return Locale.of(locales.nextToken(), locales.nextToken(), locales.nextToken());
+ default:
+ return Locale.getDefault();
+ }
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroupProvider.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroupProvider.java
new file mode 100644
index 00000000000..c4f6fec1577
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureBundleGroupProvider.java
@@ -0,0 +1,170 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Lars Vogel 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:
+ * Lars Vogel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups;
+
+import java.io.File;
+import java.io.FileFilter;
+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.internal.runtime.bundlegroups.platformxml.Configuration;
+import org.eclipse.core.internal.runtime.bundlegroups.platformxml.ConfigurationParser;
+import org.eclipse.core.internal.runtime.bundlegroups.platformxml.Feature;
+import org.eclipse.core.internal.runtime.bundlegroups.platformxml.Site;
+import org.eclipse.core.runtime.IBundleGroup;
+import org.eclipse.core.runtime.IBundleGroupProvider;
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.osgi.service.datalocation.Location;
+import org.osgi.service.component.annotations.Component;
+
+/**
+ * Contributes the installed features as bundle groups. The feature list comes
+ * from the platform.xml written at provisioning time, so no plug-in has to be
+ * inspected to build it.
+ */
+@Component(service = IBundleGroupProvider.class)
+public class FeatureBundleGroupProvider implements IBundleGroupProvider {
+
+ private static final String PLATFORM_XML = "org.eclipse.update/platform.xml"; //$NON-NLS-1$
+ private static final String FEATURES = "features"; //$NON-NLS-1$
+ private static final String FEATURE_XML = "feature.xml"; //$NON-NLS-1$
+
+ private List cachedFeatures;
+ private long cachedTimestamp = -1;
+
+ @Override
+ public String getName() {
+ return Messages.BundleGroupProvider;
+ }
+
+ @Override
+ public synchronized IBundleGroup[] getBundleGroups() {
+ File platformXml = findPlatformXml();
+ long timestamp = platformXml == null ? 0 : platformXml.lastModified();
+ if (cachedFeatures == null || timestamp != cachedTimestamp) {
+ cachedFeatures = readFeatures(platformXml);
+ cachedTimestamp = timestamp;
+ }
+ // the branding bundle of a feature can be installed after the feature was read
+ return cachedFeatures.stream().filter(FeatureBundleGroup::hasBranding).toArray(IBundleGroup[]::new);
+ }
+
+ private static List readFeatures(File platformXml) {
+ URL installArea = getInstallArea();
+ List features = new ArrayList<>();
+ Configuration configuration = platformXml == null ? null
+ : ConfigurationParser.parse(platformXml, installArea);
+ if (configuration == null) {
+ collectFromInstallArea(installArea, features);
+ } else {
+ for (Site site : configuration.getSites()) {
+ collectFromSite(site, features);
+ }
+ }
+ return features;
+ }
+
+ private static void collectFromSite(Site site, List features) {
+ URI siteLocation = toURI(site.getUrl());
+ if (siteLocation == null) {
+ return;
+ }
+ for (Feature feature : site.getFeatures()) {
+ if (feature.getId() == null) {
+ continue;
+ }
+ String featureURL = feature.getUrl();
+ if (featureURL == null) {
+ featureURL = FEATURES + "/" + feature.getId() + "_" + feature.getVersion() + "/"; //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
+ }
+ features.add(new FeatureBundleGroup(feature.getId(), feature.getVersion(), feature.getPluginIdentifier(),
+ feature.getPluginVersion(), siteLocation.resolve(featureURL)));
+ }
+ }
+
+ /*
+ * Without a platform.xml the installed features can only be found by looking at
+ * the features directory of the install area.
+ */
+ private static void collectFromInstallArea(URL installArea, List features) {
+ if (installArea == null || !"file".equals(installArea.getProtocol())) { //$NON-NLS-1$
+ return;
+ }
+ File featuresDir = new File(installArea.getFile().replace('/', File.separatorChar), FEATURES);
+ File[] dirs = featuresDir
+ .listFiles((FileFilter) f -> f.isDirectory() && new File(f, FEATURE_XML).exists());
+ if (dirs == null) {
+ return;
+ }
+ for (File dir : dirs) {
+ try {
+ FeatureManifest manifest = FeatureXmlParser.parse(new File(dir, FEATURE_XML).toURI().toURL(), true);
+ if (manifest != null) {
+ features.add(new FeatureBundleGroup(manifest.id, manifest.version, manifest.pluginIdentifier,
+ manifest.version, dir.toURI()));
+ }
+ } catch (MalformedURLException e) {
+ ILog.of(FeatureBundleGroupProvider.class).error("Cannot read the feature in " + dir, e); //$NON-NLS-1$
+ }
+ }
+ }
+
+ /*
+ * The platform.xml of the configuration area, falling back to the one of a
+ * shared install.
+ */
+ private static File findPlatformXml() {
+ Location configuration = Platform.getConfigurationLocation();
+ if (configuration == null) {
+ return null;
+ }
+ File file = toPlatformXml(configuration);
+ if (file != null && file.exists()) {
+ return file;
+ }
+ Location parent = configuration.getParentLocation();
+ File parentFile = parent == null ? null : toPlatformXml(parent);
+ return parentFile != null && parentFile.exists() ? parentFile : null;
+ }
+
+ private static File toPlatformXml(Location location) {
+ URL url = location.getURL();
+ if (url == null || !"file".equals(url.getProtocol())) { //$NON-NLS-1$
+ return null;
+ }
+ return new File(url.getFile().replace('/', File.separatorChar), PLATFORM_XML);
+ }
+
+ private static URL getInstallArea() {
+ Location install = Platform.getInstallLocation();
+ return install == null ? null : install.getURL();
+ }
+
+ private static URI toURI(String encodedURI) {
+ if (encodedURI == null) {
+ return null;
+ }
+ try {
+ return new URI(encodedURI);
+ } catch (URISyntaxException e) {
+ ILog.of(FeatureBundleGroupProvider.class).error("Cannot resolve the site " + encodedURI, e); //$NON-NLS-1$
+ return null;
+ }
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureManifest.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureManifest.java
new file mode 100644
index 00000000000..9c8a52faa72
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureManifest.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Lars Vogel 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:
+ * Lars Vogel - initial API and implementation
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * The contents of a feature.xml file, as far as a bundle group needs them.
+ */
+class FeatureManifest {
+
+ String id;
+ String version;
+ String pluginIdentifier;
+ String application;
+ boolean primary;
+ String description;
+ String licenseURL;
+ final List pluginIds = new ArrayList<>();
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureXmlParser.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureXmlParser.java
new file mode 100644
index 00000000000..4a8f2cd27bb
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/FeatureXmlParser.java
@@ -0,0 +1,195 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2017 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.StringTokenizer;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.Platform;
+import org.eclipse.osgi.util.NLS;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.helpers.DefaultHandler;
+
+/**
+ * Parses a feature.xml file. In header mode parsing stops after the feature
+ * element, which is all the platform.xml fallback needs.
+ */
+class FeatureXmlParser extends DefaultHandler {
+
+ private static final SAXException STOP_PARSING = new SAXException(""); //$NON-NLS-1$
+
+ private final boolean headerOnly;
+ private final FeatureManifest manifest = new FeatureManifest();
+ private final StringBuilder description = new StringBuilder();
+ private boolean isDescription;
+
+ static FeatureManifest parse(URL featureXML, boolean headerOnly) {
+ FeatureXmlParser handler = new FeatureXmlParser(headerOnly);
+ SAXParser parser;
+ try {
+ parser = createParser();
+ } catch (ParserConfigurationException | SAXException e) {
+ ILog.of(FeatureXmlParser.class).error("Failed to create a parser for " + featureXML, e); //$NON-NLS-1$
+ return null;
+ }
+ try (InputStream in = featureXML.openStream()) {
+ parser.parse(new InputSource(in), handler);
+ } catch (SAXException e) {
+ if (e != STOP_PARSING) {
+ return handler.manifest.id == null ? null : handler.manifest;
+ }
+ } catch (IOException e) {
+ return null;
+ }
+ return handler.manifest.id == null ? null : handler.manifest;
+ }
+
+ @SuppressWarnings("restriction")
+ private static SAXParser createParser() throws ParserConfigurationException, SAXException {
+ return org.eclipse.core.internal.runtime.XmlProcessorFactory.createSAXParserWithErrorOnDOCTYPE(true);
+ }
+
+ private FeatureXmlParser(boolean headerOnly) {
+ this.headerOnly = headerOnly;
+ }
+
+ @Override
+ public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException {
+ if ("feature".equals(localName)) { //$NON-NLS-1$
+ processFeature(attributes);
+ if (headerOnly) {
+ throw STOP_PARSING;
+ }
+ } else if ("plugin".equals(localName)) { //$NON-NLS-1$
+ processPlugin(attributes);
+ } else if ("description".equals(localName)) { //$NON-NLS-1$
+ isDescription = true;
+ } else if ("license".equals(localName)) { //$NON-NLS-1$
+ manifest.licenseURL = attributes.getValue("url"); //$NON-NLS-1$
+ }
+ }
+
+ private void processFeature(Attributes attributes) {
+ String id = attributes.getValue("id"); //$NON-NLS-1$
+ String version = attributes.getValue("version"); //$NON-NLS-1$
+ if (id == null || id.trim().isEmpty() || version == null || version.trim().isEmpty()) {
+ ILog.of(FeatureXmlParser.class).warn(NLS.bind(Messages.FeatureParser_IdOrVersionInvalid, id, version));
+ return;
+ }
+ if (!isValidEnvironment(attributes)) {
+ return;
+ }
+ manifest.id = id;
+ manifest.version = version;
+ manifest.pluginIdentifier = attributes.getValue("plugin"); //$NON-NLS-1$
+ manifest.application = attributes.getValue("application"); //$NON-NLS-1$
+ manifest.primary = "true".equals(attributes.getValue("primary")); //$NON-NLS-1$ //$NON-NLS-2$
+ }
+
+ private void processPlugin(Attributes attributes) {
+ String id = attributes.getValue("id"); //$NON-NLS-1$
+ String version = attributes.getValue("version"); //$NON-NLS-1$
+ if (id == null || id.trim().isEmpty() || version == null || version.trim().isEmpty()) {
+ ILog.of(FeatureXmlParser.class).warn(NLS.bind(Messages.FeatureParser_IdOrVersionInvalid, id, version));
+ return;
+ }
+ if (!isValidEnvironment(attributes)) {
+ return;
+ }
+ manifest.pluginIds.add(id);
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ if (isDescription) {
+ description.append(ch, start, length);
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String localName, String qName) throws SAXException {
+ if ("description".equals(localName)) { //$NON-NLS-1$
+ isDescription = false;
+ manifest.description = description.toString().trim();
+ }
+ }
+
+ private static boolean isValidEnvironment(Attributes attributes) {
+ String os = attributes.getValue("os"); //$NON-NLS-1$
+ String ws = attributes.getValue("ws"); //$NON-NLS-1$
+ String arch = attributes.getValue("arch"); //$NON-NLS-1$
+ String nl = attributes.getValue("nl"); //$NON-NLS-1$
+ if (os != null && !isMatching(os, Platform.getOS())) {
+ return false;
+ }
+ if (ws != null && !isMatching(ws, Platform.getWS())) {
+ return false;
+ }
+ if (arch != null && !isMatching(arch, Platform.getOSArch())) {
+ return false;
+ }
+ if (nl != null && !isMatchingLocale(nl, Platform.getNL())) {
+ return false;
+ }
+ return true;
+ }
+
+ private static boolean isMatching(String candidateValues, String siteValues) {
+ if (siteValues == null) {
+ return false;
+ }
+ if ("*".equalsIgnoreCase(candidateValues)) { //$NON-NLS-1$
+ return true;
+ }
+ siteValues = siteValues.toUpperCase();
+ StringTokenizer stok = new StringTokenizer(candidateValues, ","); //$NON-NLS-1$
+ while (stok.hasMoreTokens()) {
+ String token = stok.nextToken().toUpperCase();
+ if (siteValues.contains(token)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ private static boolean isMatchingLocale(String candidateValues, String locale) {
+ if (locale == null) {
+ return false;
+ }
+ if ("*".equalsIgnoreCase(candidateValues)) { //$NON-NLS-1$
+ return true;
+ }
+ locale = locale.toUpperCase();
+ candidateValues = candidateValues.toUpperCase();
+ StringTokenizer stok = new StringTokenizer(candidateValues, ","); //$NON-NLS-1$
+ while (stok.hasMoreTokens()) {
+ String candidate = stok.nextToken();
+ if (locale.indexOf(candidate) == 0) {
+ return true;
+ }
+ if (candidate.indexOf(locale) == 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IBundleGroupConstants.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IBundleGroupConstants.java
similarity index 86%
rename from update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IBundleGroupConstants.java
rename to runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IBundleGroupConstants.java
index c8c78bacc36..bcedef3c09c 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IBundleGroupConstants.java
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IBundleGroupConstants.java
@@ -11,9 +11,8 @@
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
-package org.eclipse.update.internal.configurator.branding;
+package org.eclipse.core.internal.runtime.bundlegroups;
-import org.eclipse.update.configurator.IPlatformConfiguration;
/**
* These constants define the set of properties that the UI expects to
@@ -55,15 +54,13 @@ public interface IBundleGroupConstants {
/**
* The id of the bundle group's branding bundle.
*
- * @see IPlatformConfiguration.IFeatureEntry#getFeaturePluginIdentifier()
* @since 3.3
*/
public static final String BRANDING_BUNDLE_ID= "brandingBundleId"; //$NON-NLS-1$
/**
- * The version of the feature branding bundle.
+ * The version of the bundle group's branding bundle.
*
- * @see IPlatformConfiguration.IFeatureEntry#getFeaturePluginVersion()
* @since 3.3
*/
public static final String BRANDING_BUNDLE_VERSION= "brandingBundleVersion"; //$NON-NLS-1$
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IProductConstants.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IProductConstants.java
similarity index 98%
rename from update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IProductConstants.java
rename to runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IProductConstants.java
index 2ce1a3f6665..7111b183518 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IProductConstants.java
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IProductConstants.java
@@ -11,7 +11,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
-package org.eclipse.update.internal.configurator.branding;
+package org.eclipse.core.internal.runtime.bundlegroups;
/**
* These constants define the set of properties that the UI expects to
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IniFileReader.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IniFileReader.java
similarity index 98%
rename from update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IniFileReader.java
rename to runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IniFileReader.java
index e42d4dac0bc..f4d48bef45d 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/branding/IniFileReader.java
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/IniFileReader.java
@@ -11,7 +11,7 @@
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
-package org.eclipse.update.internal.configurator.branding;
+package org.eclipse.core.internal.runtime.bundlegroups;
import java.io.IOException;
import java.io.InputStream;
@@ -31,7 +31,6 @@
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.osgi.util.NLS;
-import org.eclipse.update.internal.configurator.Messages;
import org.osgi.framework.Bundle;
import org.osgi.framework.Constants;
@@ -43,7 +42,7 @@
* properties file - format being "n = some text", where n is a number.
*/
public class IniFileReader {
- private static final String PID = "org.eclipse.update.configurator"; //$NON-NLS-1$
+ private static final String PID = "org.eclipse.core.runtime.bundlegroups"; //$NON-NLS-1$
private static final Status OK_STATUS = new Status(IStatus.OK, PID, 0, "", null); //$NON-NLS-1$
private static final String KEY_PREFIX = "%"; //$NON-NLS-1$
private static final String KEY_DOUBLE_PREFIX = "%%"; //$NON-NLS-1$
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/Messages.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/Messages.java
new file mode 100644
index 00000000000..3c08954706e
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/Messages.java
@@ -0,0 +1,37 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2008 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups;
+
+import org.eclipse.osgi.util.NLS;
+
+public final class Messages extends NLS {
+
+ private static final String BUNDLE_NAME = "org.eclipse.core.internal.runtime.bundlegroups.messages";//$NON-NLS-1$
+
+ private Messages() {
+ // Do not instantiate
+ }
+
+ public static String BundleGroupProvider;
+ public static String FeatureParser_IdOrVersionInvalid;
+ public static String IniFileReader_MissingDesc;
+ public static String IniFileReader_OpenINIError;
+ public static String IniFileReader_ReadIniError;
+ public static String IniFileReader_ReadPropError;
+ public static String IniFileReader_ReadMapError;
+
+ static {
+ NLS.initializeMessages(BUNDLE_NAME, Messages.class);
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/messages.properties b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/messages.properties
new file mode 100644
index 00000000000..67242172d1b
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/messages.properties
@@ -0,0 +1,22 @@
+###############################################################################
+# Copyright (c) 2000, 2008 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
+###############################################################################
+BundleGroupProvider=Installed Features
+
+FeatureParser_IdOrVersionInvalid= Error parsing feature stream. The unique identifier or the version is null or empty: unique identifier=\"{0}\" version=\"{1}\".
+
+IniFileReader_MissingDesc = Cannot find plugin for feature {0}
+IniFileReader_OpenINIError = Cannot open branding file {0}
+IniFileReader_ReadIniError = Error reading branding file {0}
+IniFileReader_ReadPropError = Error reading branding properties file {0}
+IniFileReader_ReadMapError = Error reading mapping files {0}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Configuration.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Configuration.java
new file mode 100644
index 00000000000..5753448e72c
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Configuration.java
@@ -0,0 +1,89 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups.platformxml;
+
+import java.io.File;
+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.URIUtil;
+
+/**
+ * The contents of a platform.xml file.
+ */
+public class Configuration {
+
+ private final List sites = new ArrayList<>();
+ private String sharedUR;
+ private URL osgiInstallArea;
+
+ void setSharedUR(String value) {
+ sharedUR = value;
+ }
+
+ void setOsgiInstallArea(URL osgiInstallArea) {
+ this.osgiInstallArea = osgiInstallArea;
+ }
+
+ /**
+ * The sites of this configuration, followed by those of the shared
+ * configuration it links to, if any.
+ */
+ public List getSites() {
+ if (sharedUR == null) {
+ return sites;
+ }
+ List result = new ArrayList<>(sites);
+ Configuration parent = loadParent();
+ if (parent != null) {
+ result.addAll(parent.getSites());
+ }
+ return result;
+ }
+
+ private Configuration loadParent() {
+ File location;
+ try {
+ location = URIUtil.toFile(new URI(sharedUR));
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ if (location == null) {
+ return null;
+ }
+ if (!location.isAbsolute()) {
+ File installArea = toFile(osgiInstallArea);
+ if (installArea == null) {
+ return null;
+ }
+ location = new File(installArea, location.getPath());
+ }
+ return ConfigurationParser.parse(location, osgiInstallArea);
+ }
+
+ private static File toFile(URL url) {
+ try {
+ return url == null ? null : URIUtil.toFile(URIUtil.toURI(url));
+ } catch (URISyntaxException e) {
+ return null;
+ }
+ }
+
+ void add(Site site) {
+ sites.add(site);
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationConstants.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationConstants.java
new file mode 100644
index 00000000000..d241a1c4fd2
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationConstants.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright (c) 2008 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups.platformxml;
+
+/**
+ * Element and attribute names used in a platform.xml file.
+ */
+public interface ConfigurationConstants {
+
+ String ELEMENT_CONFIG = "config"; //$NON-NLS-1$
+ String ELEMENT_FEATURE = "feature"; //$NON-NLS-1$
+ String ELEMENT_SITE = "site"; //$NON-NLS-1$
+
+ String ATTRIBUTE_ID = "id"; //$NON-NLS-1$
+ String ATTRIBUTE_PLUGIN_IDENTIFIER = "plugin-identifier"; //$NON-NLS-1$
+ String ATTRIBUTE_PLUGIN_VERSION = "plugin-version"; //$NON-NLS-1$
+ String ATTRIBUTE_SHARED_UR = "shared_ur"; //$NON-NLS-1$
+ String ATTRIBUTE_URL = "url"; //$NON-NLS-1$
+ String ATTRIBUTE_VERSION = "version"; //$NON-NLS-1$
+
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationParser.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationParser.java
new file mode 100644
index 00000000000..dc9cf4bf4d4
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/ConfigurationParser.java
@@ -0,0 +1,227 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2015 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups.platformxml;
+
+import java.io.BufferedInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.net.URL;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+
+import org.eclipse.core.runtime.ILog;
+import org.eclipse.core.runtime.URIUtil;
+import org.w3c.dom.Document;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+/**
+ * Parser for platform.xml files.
+ */
+public class ConfigurationParser implements ConfigurationConstants {
+ static final String PLATFORM_BASE = "platform:/base/"; //$NON-NLS-1$
+ private final URL osgiInstallArea;
+
+ /*
+ * Parse the given file handle which points to a platform.xml file and return a
+ * configuration object. Returns null if the file doesn't exist or cannot be read.
+ */
+ public static Configuration parse(File file, URL osgiInstallArea) {
+ return new ConfigurationParser(osgiInstallArea).internalParse(file);
+ }
+
+ private ConfigurationParser(URL osgiInstallArea) {
+ this.osgiInstallArea = osgiInstallArea;
+ }
+
+ /*
+ * Create a feature object based on the given DOM node. Return the new feature.
+ */
+ private static Feature createFeature(Node node) {
+ Feature result = new Feature();
+ String id = getAttribute(node, ATTRIBUTE_ID);
+ if (id != null) {
+ result.setId(id);
+ }
+ String url = getAttribute(node, ATTRIBUTE_URL);
+ if (url != null) {
+ result.setUrl(url);
+ }
+ String version = getAttribute(node, ATTRIBUTE_VERSION);
+ if (version != null) {
+ result.setVersion(version);
+ }
+ String pluginIdentifier = getAttribute(node, ATTRIBUTE_PLUGIN_IDENTIFIER);
+ if (pluginIdentifier != null) {
+ result.setPluginIdentifier(pluginIdentifier);
+ }
+ String pluginVersion = getAttribute(node, ATTRIBUTE_PLUGIN_VERSION);
+ // plug-in version is the same as the feature version if it is missing
+ if (pluginVersion == null) {
+ pluginVersion = version;
+ }
+ if (pluginVersion != null) {
+ result.setPluginVersion(pluginVersion);
+ }
+ return result;
+ }
+
+ /*
+ * Create the features from the given DOM node.
+ */
+ private static void createFeatures(Node node, Site site) {
+ NodeList children = node.getChildNodes();
+ int size = children.getLength();
+ for (int i = 0; i < size; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (!ELEMENT_FEATURE.equalsIgnoreCase(child.getNodeName())) {
+ continue;
+ }
+ site.addFeature(createFeature(child));
+ }
+ }
+
+ /*
+ * Create a site based on the given DOM node.
+ */
+ private Site createSite(Node node) {
+ Site result = new Site();
+ String url = getAttribute(node, ATTRIBUTE_URL);
+ if (url != null) {
+ try {
+ // do this to ensure the location is an encoded URI
+ URI uri = URIUtil.fromString(url);
+ URI osgiURI = osgiInstallArea != null ? URIUtil.toURI(osgiInstallArea) : null;
+ result.setUrl(getLocation(uri, osgiURI).toString());
+ } catch (URISyntaxException e) {
+ result.setUrl(url);
+ }
+ }
+ createFeatures(node, result);
+ return result;
+ }
+
+ /*
+ * Convert the given url string to an absolute url. If the string is
+ * platform:/base/ then return a string which represents the osgi install area.
+ */
+ private static URI getLocation(URI location, URI osgiArea) {
+ if (osgiArea == null) {
+ return location;
+ }
+ if (PLATFORM_BASE.equals(location.toString())) {
+ return osgiArea;
+ }
+ return URIUtil.makeAbsolute(location, osgiArea);
+ }
+
+ /*
+ * Return the attribute with the given name, or null if it does not exist.
+ */
+ private static String getAttribute(Node node, String name) {
+ NamedNodeMap attributes = node.getAttributes();
+ Node temp = attributes.getNamedItem(name);
+ return temp == null ? null : temp.getNodeValue();
+ }
+
+ /*
+ * Load the given file into a DOM document.
+ */
+ @SuppressWarnings("restriction")
+ private static Document load(InputStream input) throws ParserConfigurationException, IOException, SAXException {
+ DocumentBuilderFactory factory = org.eclipse.core.internal.runtime.XmlProcessorFactory
+ .createDocumentBuilderFactoryWithErrorOnDOCTYPE();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ try (InputStream buffered = new BufferedInputStream(input)) {
+ return builder.parse(buffered);
+ }
+ }
+
+ private Configuration internalParse(File file) {
+ if (!file.exists()) {
+ return null;
+ }
+ try (InputStream input = new FileInputStream(file)) {
+ Configuration result = process(load(input));
+ if (result != null) {
+ result.setOsgiInstallArea(osgiInstallArea);
+ }
+ return result;
+ } catch (IOException | ParserConfigurationException | SAXException e) {
+ ILog.of(ConfigurationParser.class).error("Failed to read " + file, e); //$NON-NLS-1$
+ return null;
+ }
+ }
+
+ /*
+ * Process the given DOM document and create the appropriate site objects.
+ */
+ private Configuration process(Document document) {
+ Node node = getConfigElement(document);
+ if (node == null) {
+ return null;
+ }
+ Configuration configuration = createConfiguration(node);
+ NodeList children = node.getChildNodes();
+ int size = children.getLength();
+ for (int i = 0; i < size; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (!ELEMENT_SITE.equalsIgnoreCase(child.getNodeName())) {
+ continue;
+ }
+ Site site = createSite(child);
+ if (site != null) {
+ configuration.add(site);
+ }
+ }
+ return configuration;
+ }
+
+ private static Configuration createConfiguration(Node node) {
+ Configuration result = new Configuration();
+ String value = getAttribute(node, ATTRIBUTE_SHARED_UR);
+ if (value != null) {
+ result.setSharedUR(value);
+ }
+ return result;
+ }
+
+ private static Node getConfigElement(Document doc) {
+ NodeList children = doc.getChildNodes();
+ int size = children.getLength();
+ for (int i = 0; i < size; i++) {
+ Node child = children.item(i);
+ if (child.getNodeType() != Node.ELEMENT_NODE) {
+ continue;
+ }
+ if (ELEMENT_CONFIG.equalsIgnoreCase(child.getNodeName())) {
+ return child;
+ }
+ }
+ return null;
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Feature.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Feature.java
new file mode 100644
index 00000000000..222cf79a969
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Feature.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2005, 2008 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups.platformxml;
+
+/*
+ * Represents a feature entry in a platform.xml file.
+ */
+public class Feature {
+
+ private String id;
+ private String url;
+ private String version;
+ private String pluginIdentifier;
+ private String pluginVersion;
+
+ public String getId() {
+ return id;
+ }
+
+ public String getPluginIdentifier() {
+ return pluginIdentifier;
+ }
+
+ public String getPluginVersion() {
+ return pluginVersion;
+ }
+
+ public String getUrl() {
+ return url;
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setPluginIdentifier(String pluginIdentifier) {
+ this.pluginIdentifier = pluginIdentifier;
+ }
+
+ public void setPluginVersion(String pluginVersion) {
+ this.pluginVersion = pluginVersion;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public void setVersion(String version) {
+ this.version = version;
+ }
+}
diff --git a/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Site.java b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Site.java
new file mode 100644
index 00000000000..8348e99a4dc
--- /dev/null
+++ b/runtime/bundles/org.eclipse.core.runtime.bundlegroups/src/org/eclipse/core/internal/runtime/bundlegroups/platformxml/Site.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008, 2010 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
+ *******************************************************************************/
+package org.eclipse.core.internal.runtime.bundlegroups.platformxml;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/*
+ * Represents a site in a platform.xml file.
+ */
+public class Site {
+
+ private String url;
+ private final List features = new ArrayList<>();
+
+ public void addFeature(Feature feature) {
+ this.features.add(feature);
+ }
+
+ public List getFeatures() {
+ return features;
+ }
+
+ /**
+ * Note the string that we are returning is an ENCODED URI string.
+ */
+ public String getUrl() {
+ return url;
+ }
+
+ /**
+ * Note that the string should be an ENCODED URI string.
+ */
+ public void setUrl(String url) {
+ this.url = url;
+ }
+}
diff --git a/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/configurator/tests/AutomatedConfiguratorSuite.java b/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/configurator/tests/AutomatedConfiguratorSuite.java
index 6c6e2cbaf54..4dbe8d282a3 100644
--- a/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/configurator/tests/AutomatedConfiguratorSuite.java
+++ b/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/configurator/tests/AutomatedConfiguratorSuite.java
@@ -13,7 +13,6 @@
*******************************************************************************/
package org.eclipse.update.configurator.tests;
-import org.eclipse.update.internal.configurator.tests.FeatureEntryTests;
import org.eclipse.update.internal.configurator.tests.SiteEntryTests;
import org.junit.platform.suite.api.SelectClasses;
import org.junit.platform.suite.api.Suite;
@@ -25,7 +24,6 @@
*/
@Suite
@SelectClasses({ //
- FeatureEntryTests.class, //
SiteEntryTests.class, //
})
public class AutomatedConfiguratorSuite {
diff --git a/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/internal/configurator/tests/FeatureEntryTests.java b/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/internal/configurator/tests/FeatureEntryTests.java
deleted file mode 100644
index 8072cf4543a..00000000000
--- a/update/org.eclipse.update.configurator.tests/src/org/eclipse/update/internal/configurator/tests/FeatureEntryTests.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2019, 2020 Torbjörn Svensson 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:
- * Torbjörn Svensson - initial API and implementation
- *******************************************************************************/
-package org.eclipse.update.internal.configurator.tests;
-
-import static org.junit.jupiter.api.Assertions.assertEquals;
-
-import java.net.MalformedURLException;
-import java.net.URL;
-
-import org.eclipse.update.internal.configurator.FeatureEntry;
-import org.eclipse.update.internal.configurator.SiteEntry;
-import org.junit.jupiter.api.Test;
-
-@SuppressWarnings("restriction")
-public class FeatureEntryTests {
-
- @Test
- public void testLicenseUrl() throws MalformedURLException {
- FeatureEntry featureEntry = new FeatureEntry("org.eclipse.platform", "4.13.0.v20190916-1323", null, null, false, null, null);
- featureEntry.setSite(new SiteEntry(new URL("platform:/base/")));
- featureEntry.setURL("features/org.eclipse.platform_4.13.0.v20190916-1323/");
-
- featureEntry.setLicenseURL("http://www.example.org");
- assertEquals("http://www.example.org", featureEntry.getLicenseURL());
-
- featureEntry.setLicenseURL("https://www.example.org");
- assertEquals("https://www.example.org", featureEntry.getLicenseURL());
-
- featureEntry.setLicenseURL("license.html");
- assertEquals("platform:/base/features/org.eclipse.platform_4.13.0.v20190916-1323/license.html", featureEntry.getLicenseURL());
- }
-}
\ No newline at end of file
diff --git a/update/org.eclipse.update.configurator/META-INF/MANIFEST.MF b/update/org.eclipse.update.configurator/META-INF/MANIFEST.MF
index a4f3934ac99..d28d69b429b 100644
--- a/update/org.eclipse.update.configurator/META-INF/MANIFEST.MF
+++ b/update/org.eclipse.update.configurator/META-INF/MANIFEST.MF
@@ -7,8 +7,7 @@ Bundle-Activator: org.eclipse.update.internal.configurator.ConfigurationActivato
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Export-Package: org.eclipse.update.configurator,
- org.eclipse.update.internal.configurator;x-friends:="org.eclipse.update.core",
- org.eclipse.update.internal.configurator.branding;x-friends:="org.eclipse.update.core"
+ org.eclipse.update.internal.configurator;x-friends:="org.eclipse.update.core"
Require-Bundle: org.eclipse.core.runtime;bundle-version="3.29.0"
Bundle-RequiredExecutionEnvironment: JavaSE-17
Import-Package: javax.xml.parsers,
@@ -17,5 +16,4 @@ Import-Package: javax.xml.parsers,
org.xml.sax.helpers
Bundle-ActivationPolicy: lazy
Automatic-Module-Name: org.eclipse.update.configurator
-Service-Component: OSGI-INF/org.eclipse.update.internal.configurator.BundleGroupComponent.xml,
- OSGI-INF/org.eclipse.update.internal.configurator.PlatformConfigurationFactory.xml
+Service-Component: OSGI-INF/org.eclipse.update.internal.configurator.PlatformConfigurationFactory.xml
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/BundleGroupComponent.java b/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/BundleGroupComponent.java
deleted file mode 100644
index 113d20ce3ad..00000000000
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/BundleGroupComponent.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2009, 2017 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
- *******************************************************************************/
-package org.eclipse.update.internal.configurator;
-
-import java.util.ArrayList;
-
-import org.eclipse.core.runtime.IBundleGroup;
-import org.eclipse.core.runtime.IBundleGroupProvider;
-import org.osgi.service.component.annotations.Activate;
-import org.osgi.service.component.annotations.Component;
-import org.osgi.service.component.annotations.Reference;
-
-/**
- * Declarative services component that provides an implementation of
- * {@link IBundleGroupProvider}. This allows the bundle group provider to be
- * made available in the service registry before this bundle has started.
- */
-@Component(service = IBundleGroupProvider.class)
-@SuppressWarnings("removal")
-public class BundleGroupComponent implements IBundleGroupProvider {
-
-
- private final org.eclipse.update.configurator.IPlatformConfigurationFactory factory;
-
- @Activate
- public BundleGroupComponent(@Reference org.eclipse.update.configurator.IPlatformConfigurationFactory factory) {
- this.factory = factory;
- }
-
- @Override
- public IBundleGroup[] getBundleGroups() {
- org.eclipse.update.configurator.IPlatformConfiguration configuration = factory
- .getCurrentPlatformConfiguration();
- if (configuration == null) {
- return new IBundleGroup[0];
- }
- org.eclipse.update.configurator.IPlatformConfiguration.IFeatureEntry[] features = configuration
- .getConfiguredFeatureEntries();
- ArrayList bundleGroups = new ArrayList<>(features.length);
- for (org.eclipse.update.configurator.IPlatformConfiguration.IFeatureEntry feature : features) {
- if (feature instanceof FeatureEntry && ((FeatureEntry) feature).hasBranding()) {
- bundleGroups.add((IBundleGroup) feature);
- }
- }
- return bundleGroups.toArray(new IBundleGroup[bundleGroups.size()]);
- }
-
- @Override
- public String getName() {
- return Messages.BundleGroupProvider;
- }
-
-}
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/FeatureEntry.java b/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/FeatureEntry.java
index 80806881707..6a568fdbf50 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/FeatureEntry.java
+++ b/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/FeatureEntry.java
@@ -13,20 +13,10 @@
*******************************************************************************/
package org.eclipse.update.internal.configurator;
-import java.net.MalformedURLException;
import java.net.URL;
-import java.net.URLClassLoader;
import java.util.ArrayList;
-import java.util.MissingResourceException;
-import java.util.ResourceBundle;
-import org.eclipse.core.runtime.IBundleGroup;
-import org.eclipse.core.runtime.Platform;
import org.eclipse.update.configurator.IPlatformConfiguration;
-import org.eclipse.update.internal.configurator.branding.AboutInfo;
-import org.eclipse.update.internal.configurator.branding.IBundleGroupConstants;
-import org.eclipse.update.internal.configurator.branding.IProductConstants;
-import org.osgi.framework.Bundle;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
@@ -35,13 +25,7 @@
*
* Feature information
*/
-public class FeatureEntry
- implements
- IPlatformConfiguration.IFeatureEntry,
- IConfigurationConstants,
- IBundleGroup,
- IBundleGroupConstants,
- IProductConstants {
+public class FeatureEntry implements IPlatformConfiguration.IFeatureEntry, IConfigurationConstants {
private final String id;
private final String version;
private final String pluginVersion;
@@ -50,12 +34,8 @@ public class FeatureEntry
private final boolean primary;
private final String pluginIdentifier;
private String url;
- private String description;
- private String licenseURL;
private ArrayList plugins;
- private AboutInfo branding;
private SiteEntry site;
- private ResourceBundle resourceBundle;
private boolean fullyParsed;
public FeatureEntry(String id, String version, String pluginIdentifier, String pluginVersion, boolean primary, String application, URL[] root) {
@@ -190,112 +170,6 @@ public Element toXML(Document doc) {
return featureElement;
}
- public void setDescription(String description) {
- this.description = description;
- }
-
- @Override
- public Bundle[] getBundles() {
- if (plugins == null) {
- fullParse();
- }
-
- ArrayList bundles = new ArrayList<>(plugins.size());
- for (PluginEntry plugin : plugins) {
- // get the highest version for the plugin
- Bundle bundle = Platform.getBundle(plugin.getPluginIdentifier());
- if (bundle != null) {
- bundles.add(bundle);
- }
- }
- return bundles.toArray(new Bundle[bundles.size()]);
- }
-
- @Override
- public String getDescription() {
- if (description == null) {
- fullParse();
- }
- return description;
- }
-
- @Override
- public String getIdentifier() {
- return id;
- }
-
- @Override
- public String getName() {
- if (branding == null) {
- branding = AboutInfo.readFeatureInfo(id, version, getFeaturePluginIdentifier());
- }
- return branding.getProductName();
- }
-
- @Override
- public String getProperty(String key) {
- if (key == null) {
- return null;
- }
-
- if (branding == null) {
- branding = AboutInfo.readFeatureInfo(id, version, getFeaturePluginIdentifier());
- }
-
- // IBundleGroupConstants
- if (key.equals(FEATURE_IMAGE)) {
- return branding.getFeatureImageURL() == null ? null : branding.getFeatureImageURL().toExternalForm();
- } else if (key.equals(TIPS_AND_TRICKS_HREF)) {
- return branding.getTipsAndTricksHref();
- } else if (key.equals(IBundleGroupConstants.WELCOME_PAGE)) { // same value is used by product and bundle group
- return branding.getWelcomePageURL() == null ? null : branding.getWelcomePageURL().toExternalForm();
- } else if (key.equals(WELCOME_PERSPECTIVE)) {
- return branding.getWelcomePerspectiveId();
- } else if (key.equals(BRANDING_BUNDLE_ID)) {
- return pluginIdentifier;
- } else if (key.equals(BRANDING_BUNDLE_VERSION)) {
- return pluginVersion;
- } else if (key.equals(APP_NAME)) {
- return branding.getAppName();
- } else if (key.equals(ABOUT_TEXT)) {
- return branding.getAboutText();
- } else if (key.equals(ABOUT_IMAGE)) {
- return branding.getAboutImageURL() == null ? null : branding.getAboutImageURL().toExternalForm();
- } else if (key.equals(WINDOW_IMAGE)) {
- return branding.getWindowImageURL()== null ? null : branding.getWindowImageURL().toExternalForm();
- } else if (key.equals(WINDOW_IMAGES)) {
- URL[] urls = branding.getWindowImagesURLs();
- if (urls == null) {
- return null;
- }
- StringBuilder windowImagesURLs = new StringBuilder();
- for (int i=0; i
- * For example, assume resource bundle plugin.properties contains
- * name = Project Name
- *
- *
- * resolveNLString(b,"Hello World") returns "Hello World"
- * resolveNLString(b,"%name") returns "Project Name"
- * resolveNLString(b,"%name Hello World") returns "Project Name"
- * resolveNLString(b,"%abcd Hello World") returns "Hello World"
- * resolveNLString(b,"%abcd") returns "%abcd"
- * resolveNLString(b,"%%name") returns "%name"
- *
- *
- * @param resourceBundle resource bundle.
- * @param string translatable string from model
- * @return string, or null
- * @since 2.0
- */
- public static String getResourceString(ResourceBundle resourceBundle, String string) {
-
- if (string == null) {
- return null;
- }
-
- String s = string.trim();
-
- if (s.isEmpty()) {
- return string;
- }
-
- if (!s.startsWith(KEY_PREFIX)) {
- return string;
- }
-
- if (s.startsWith(KEY_DOUBLE_PREFIX)) {
- return s.substring(1);
- }
-
- int ix = s.indexOf(" "); //$NON-NLS-1$
- String key = ix == -1 ? s : s.substring(0, ix);
- String dflt = ix == -1 ? s : s.substring(ix + 1);
-
- if (resourceBundle == null) {
- return dflt;
- }
-
- try {
- return resourceBundle.getString(key.substring(1));
- } catch (MissingResourceException e) {
- return dflt;
- }
- }
public static boolean isAutomaticallyStartedBundle(String bundleURL) {
if (bundleURL.contains("org.eclipse.osgi")) { //$NON-NLS-1$
diff --git a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/messages.properties b/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/messages.properties
index 0b2f4c3af9b..1d0bca48acc 100644
--- a/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/messages.properties
+++ b/update/org.eclipse.update.configurator/src/org/eclipse/update/internal/configurator/messages.properties
@@ -27,16 +27,10 @@ BundleManifest_noVersion = {0} attribute not found in manifest for bundle \"{1}\
FeatureParser_IdOrVersionInvalid= Error parsing feature stream. The unique identifier or the version is null or empty: unique identifier=\"{0}\" version=\"{1}\".
-BundleGroupProvider=Update Manager Configurator
ConfigurationParser_cannotLoadSharedInstall=Could not load from shared install
PluginEntry_versionError=Unable to create versioned identifier: {0} {1}
-IniFileReader_MissingDesc = Cannot find plugin for feature {0}
-IniFileReader_OpenINIError = Cannot open branding file {0}
-IniFileReader_ReadIniError = Error reading branding file {0}
-IniFileReader_ReadPropError = Error reading branding properties file {0}
-IniFileReader_ReadMapError = Error reading mapping files {0}
SiteEntry_computePluginStamp = Cannot compute plugins change stamp for site {0} because it does not support plugin detection.
SiteEntry_cannotFindFeatureInDir=Unable to find feature.xml in directory: {0}