Skip to content

[FEA] Display and Navigation of (merged) properties for includes - #7356

Open
peterkir wants to merge 1 commit into
bndtools:masterfrom
peterkir:fea-bnd-gui-include
Open

[FEA] Display and Navigation of (merged) properties for includes#7356
peterkir wants to merge 1 commit into
bndtools:masterfrom
peterkir:fea-bnd-gui-include

Conversation

@peterkir

@peterkir peterkir commented Aug 3, 2026

Copy link
Copy Markdown
Contributor
  • AbstractRequirementListPart: detect when requirements are inherited from an included file and show them greyed out; double-click opens the source file
  • AbstractRequirementListPart: add abstract getPrimaryPropertyKey() used to check the correct stem for local vs merged property detection
  • BndEditModel: add isLocalProperty, hasLocalMergeProperty, getMergedRequirements and getPropertyProvenance to support the above
  • RunRequirementsPart: implement getPrimaryPropertyKey and fall back to getMergedRequirements(-runrequires) when no local -runrequires is present
  • RunBlacklistPart: same treatment for -runblacklist
  • RepositoryBundleSelectionPart: remove unused ResourceUtils import

@chrisrueger chrisrueger left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added some comments for our next discussion about BndEditModel.

Comment on lines +1208 to +1210
public boolean isLocalProperty(String key) {
return documentProperties.containsKey(key);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest using the existing private isLocalPropertyKey()

Suggested change
public boolean isLocalProperty(String key) {
return documentProperties.containsKey(key);
}
public boolean isLocalProperty(String key) {
return isLocalPropertyKey(key);
}

Or we make the private method public.

Comment on lines +1213 to +1237
public boolean hasLocalMergeProperty(String stem) {
if (documentProperties.containsKey(stem))
return true;
String prefix = stem + ".";
return documentProperties.stringPropertyNames()
.stream()
.anyMatch(k -> k.startsWith(prefix));
}

/** Returns requirements merged across all stem.* variants from the owner processor. */
public List<Requirement> getMergedRequirements(String stem) {
Processor p = getOwner();
if (p == null)
return null;
String merged = p.mergeProperties(stem);
if (merged == null || merged.isBlank())
return null;
return requirementListConverter.convert(merged);
}

/** Returns the provenance (absolute file path) of the given property key, or empty if local or unknown. */
public Optional<String> getPropertyProvenance(String key) {
Processor p = getOwner();
if (p == null)
return Optional.empty();

@chrisrueger chrisrueger Aug 3, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss which of the new public convenience shortcuts we really need.
I am a bit hesitant since they increase the public API-surface of BndEditModel we have to maintain and I am not yet sure if they add enough value (yes they add value, but not sure if it justifies a new public method).

Maybe we could start with a helper util class in bndtools.core to make access more convenient and then see if it works in BndSourceEffectivePage.java and the RunRequirements lists.

// Instead of: model.getMergedRequirements(Constants.RUNREQUIRES)
Processor p = model.getOwner();
String merged = p.mergeProperties(Constants.RUNREQUIRES);
List<Requirement> reqs = requirementListConverter.convert(merged);
// Instead of: model.getPropertyProvenance(primaryKey)
PropertyKey.findVisible(model.getOwner().getMergePropertyKeys(primaryKey))
    .stream()
    .findFirst()
    .flatMap(PropertyKey::getProvenance);

Quick Example of a convenience accessor Util, living in bndtools.core alongside the UI code that needs it:

package bndtools.editor.project;

import java.util.List;
import java.util.Optional;

import org.osgi.resource.Requirement;

import aQute.bnd.build.model.BndEditModel;
import aQute.bnd.build.model.conversions.RequirementListConverter;
import aQute.bnd.osgi.Processor;
import aQute.bnd.osgi.Processor.PropertyKey;

/**
 * Utility accessor for BndEditModel that provides convenience methods
 * for checking property provenance and merged properties.
 * Used by UI components that need to distinguish between local and inherited properties.
 */
public class BndEditModelAccessor {
    
    private final BndEditModel model;
    private final RequirementListConverter requirementListConverter = new RequirementListConverter();
    
    public BndEditModelAccessor(BndEditModel model) {
        this.model = model;
    }
    
    /** Returns true if the given property key is defined locally, not inherited from an included file. */
    public boolean isLocalProperty(String key) {
        return model.getDocumentProperties().containsKey(key);
    }
    
    /** Returns true if any local property key matches the stem or a stem.* variant. */
    public boolean hasLocalMergeProperty(String stem) {
        if (model.getDocumentProperties().containsKey(stem))
            return true;
        String prefix = stem + ".";
        return model.getDocumentProperties().stringPropertyNames()
            .stream()
            .anyMatch(k -> k.startsWith(prefix));
    }
    
    /** Returns requirements merged across all stem.* variants from the owner processor. */
    public List<Requirement> getMergedRequirements(String stem) {
        Processor p = model.getOwner();
        if (p == null)
            return null;
        String merged = p.mergeProperties(stem);
        if (merged == null || merged.isBlank())
            return null;
        return requirementListConverter.convert(merged);
    }
    
    /** Returns the provenance (absolute file path) of the given property key, or empty if local or unknown. */
    public Optional<String> getPropertyProvenance(String key) {
        Processor p = model.getOwner();
        if (p == null)
            return Optional.empty();
        return PropertyKey.findVisible(p.getMergePropertyKeys(key))
            .stream()
            .findFirst()
            .flatMap(PropertyKey::getProvenance);
    }
}

peterkir added a commit to peterkir/bnd that referenced this pull request Aug 5, 2026
…use accessor utility

- Remove 4 new public methods from BndEditModel (isLocalProperty, hasLocalMergeProperty, getMergedRequirements, getPropertyProvenance)
- Revert package-info.java version to 4.5.0 (no public API expansion = no MINOR bump)
- Create BndEditModelAccessor (package-private) utility with static accessor methods for UI components
- Update AbstractRequirementListPart, RunRequirementsPart, RunBlacklistPart to use accessor methods
- Add missing convertRepoFeature() method to RepositoryBundleUtils for feature syntax handling

This addresses reviewer feedback on PR bndtools#7356 to avoid expanding the public API while maintaining feature functionality.

Signed-off-by: Peter Kirschner <peter@klib.io>
@peterkir
peterkir force-pushed the fea-bnd-gui-include branch from bb8e951 to 5052796 Compare August 5, 2026 16:54
@peterkir peterkir changed the title fea: display and navigate merged properties from included bnd files [FEA] Display and Navigation of (merged) properties for includes Aug 6, 2026
@peterkir
peterkir force-pushed the fea-bnd-gui-include branch from 5052796 to 3d6656d Compare August 6, 2026 06:38
Signed-off-by: Peter Kirschner <peter@kirschners.de>
@peterkir
peterkir force-pushed the fea-bnd-gui-include branch from 3d6656d to 0857c8c Compare August 7, 2026 19:03
@chrisrueger

chrisrueger commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

@peterkir Feedback: Did a quick test with this PR in our project.
Display works great (I can see the greyed out inherited entries and click on it and go to the parent (provenance) file directly 👍

One issue i discovered:

  • I added some an entry to Run Blacklist (via the + button) in a shared.bnd (the one I include)
  • entry was added
  • but when I save I get this error:
image
Save Failed
class java.lang.String cannot be cast to class java.util.List (java.lang.String and java.util.List are in module java.base of loader 'bootstrap')

Unfortunatelly a stacktrace does not get logged but I could obtain one via debugger manually:

java.lang.ClassCastException: class java.lang.String cannot be cast to class java.util.List (java.lang.String and java.util.List are in module java.base of loader 'bootstrap')
	at aQute.bnd.build.model.BndEditModel.getRunRequires(BndEditModel.java:1199)
	at aQute.bnd.build.model.BndEditModel.setRunRequires(BndEditModel.java:1203)
	at bndtools.editor.project.RunRequirementsPart.doCommitToModel(RunRequirementsPart.java:188)
	at bndtools.editor.project.AbstractRequirementListPart.commitToModel(AbstractRequirementListPart.java:284)
	at bndtools.editor.common.BndEditorPart.commit(BndEditorPart.java:82)
	at org.eclipse.ui.forms.ManagedForm.commit(ManagedForm.java:193)
	at org.eclipse.ui.forms.editor.FormEditor.commitPages(FormEditor.java:299)
	at bndtools.editor.BndEditor.commitDirtyPages(BndEditor.java:275)
	at bndtools.editor.BndEditor.doSave(BndEditor.java:251)

The ClassCastException is not that obvious, but it happens here:

image

To me it seems that the caller expects a List<Requirement> but aQute.bnd.build.model.BndEditModel.doGetObject(String, Converter<? extends R, ? super String>, boolean) returns a plain String in the first if branch , since it is not using the converter.

I could fix it by changing from:

if (objectProperties.containsKey(name)) {
				@SuppressWarnings("unchecked")
				R temp = (R) objectProperties.get(name);
				result = temp;
			}

to

if (objectProperties.containsKey(name)) {
	return converter.convert((String) objectProperties.get(name));
}
image

But let's not do this as fix. I think the actual bug is in:

aQute.bnd.build.model.BndEditModel.doSetObject(String, T, T, Converter<String, ? super T>)
which puts the un-formatted value into the objectProperties cache which is the root cause for the ClassCastException.

image

When I change this to objectProperties.put(name, v); the problem with the ClassCastException above is also fix and saving works.

...something like that

UPDATE: I think the problem may have been introduced by

https://github.com/bndtools/bnd/pull/7356/changes#diff-af324c809dd4117f47e1a35dbb0318547a295dce78737367d08a57d612eee73dR92

/** Returns requirements to write to an arbitrary merge key using the standard requirements format. */
	static void setRequirementListByKey(BndEditModel model, String key, List<Requirement> requires) {
		model.setGenericString(key, requirementListFormatter.convert(requires));
	}

I think we should not use setGenericString(). Instead we should use some combination of:

BndEditModel.getConverter(Map, String)

to get a known converter
and then find a way to call

BndEditModel.doSetObject(String, T, T, Converter<String, ? super T>)

with that converter, in case we don't know exactly with what keys we are dealing with.
If you do a reference search fosetGenericString() you notice that this not called often in existing code (only for setResolveMode())

So I think we should revist the new methods of this PR:

  • bndtools.editor.project.BndEditModelAccessor.setPropertiesByKey(BndEditModel, String, Map<String, String>)
  • bndtools.editor.project.BndEditModelAccessor.setRequirementListByKey(BndEditModel, String, List<Requirement>)
  • bndtools.editor.project.BndEditModelAccessor.setVersionedClausesByKey(BndEditModel, String, List<VersionedClause>)

and the change this PR made to bndtools.editor.project.RunPropertiesPart.commitToModel(boolean)

@chrisrueger

chrisrueger commented Aug 8, 2026

Copy link
Copy Markdown
Contributor

Another issue: Scrollbars are missing and scrolling is not working in the RunrequiresPart and BlacklistPart
(I noticed in the Eclipse debug instance. ).

image

In my current main Eclipse I see scroll bars:

image

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants