Skip to content

Commit ac47adb

Browse files
committed
model updates
Signed-off-by: Peter Kirschner <peter@klib.io>
1 parent 3d6656d commit ac47adb

9 files changed

Lines changed: 804 additions & 154 deletions

bndtools.core/src/bndtools/editor/BndEditor.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.eclipse.core.runtime.IProgressMonitor;
4242
import org.eclipse.core.runtime.IStatus;
4343
import org.eclipse.core.runtime.NullProgressMonitor;
44+
import org.eclipse.core.runtime.Path;
4445
import org.eclipse.core.runtime.Platform;
4546
import org.eclipse.core.runtime.Status;
4647
import org.eclipse.core.runtime.jobs.IJobChangeEvent;
@@ -775,6 +776,40 @@ public void resourceChanged(IResourceChangeEvent event) {
775776
IResourceDelta delta = event.getDelta();
776777
if (delta == null)
777778
return;
779+
780+
// When an included file is saved, refresh the owner processor and reload
781+
// so merged properties (e.g. -runrequires, -runproperties) reflect the change.
782+
aQute.bnd.osgi.Processor owner = model.getOwner();
783+
if (owner != null && !saving.get()) {
784+
final IResourceDelta fullDelta = delta; // delta is reassigned below; capture before
785+
boolean includedChanged = owner.getIncluded()
786+
.stream()
787+
.anyMatch(includedFile -> {
788+
IFile wsFile = ResourcesPlugin.getWorkspace()
789+
.getRoot()
790+
.getFileForLocation(new Path(includedFile.getAbsolutePath()));
791+
if (wsFile == null)
792+
return false;
793+
IResourceDelta d = fullDelta.findMember(wsFile.getFullPath());
794+
return d != null
795+
&& (d.getKind() & IResourceDelta.CHANGED) != 0
796+
&& (d.getFlags() & IResourceDelta.CONTENT) != 0;
797+
});
798+
if (includedChanged) {
799+
final IDocumentProvider docProvider = sourcePage.getDocumentProvider();
800+
if (docProvider != null) {
801+
final IDocument document = docProvider.getDocument(getEditorInput());
802+
SWTConcurrencyUtil.execForControl(getEditorSite().getShell(), true, () -> {
803+
try {
804+
owner.forceRefresh();
805+
model.loadFrom(new IDocumentWrapper(document));
806+
} catch (IOException e) {
807+
logger.logError("Failed to reload model after included file change", e);
808+
}
809+
});
810+
}
811+
}
812+
}
778813
IPath fullPath = myResource.getFullPath();
779814
delta = delta.findMember(fullPath);
780815
if (delta == null)

bndtools.core/src/bndtools/editor/pages/ProjectRunPage.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -182,13 +182,15 @@ protected void createFormContent(IManagedForm managedForm) {
182182
.setLayoutData(PageLayoutUtils.createCollapsed());
183183

184184
AvailableBundlesPart availableBundlesPart = new AvailableBundlesPart(left, tk,
185-
ExpandableComposite.TITLE_BAR | ExpandableComposite.EXPANDED);
185+
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED);
186186
managedForm.addPart(availableBundlesPart);
187187
gd = new GridData(SWT.FILL, SWT.FILL, true, true);
188188
gd.widthHint = 50;
189189
gd.heightHint = 50;
190190
availableBundlesPart.getSection()
191191
.setLayoutData(PageLayoutUtils.createExpanded());
192+
availableBundlesPart.getSection()
193+
.addExpansionListener(new ResizeExpansionAdapter(availableBundlesPart.getSection()));
192194

193195
RunFrameworkPart runFwkPart = new RunFrameworkPart(left, tk,
194196
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED);
@@ -198,11 +200,12 @@ protected void createFormContent(IManagedForm managedForm) {
198200
.setLayoutData(gd);
199201

200202
RunPropertiesPart runPropertiesPart = new RunPropertiesPart(left, tk,
201-
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE);
203+
ExpandableComposite.TITLE_BAR | ExpandableComposite.TWISTIE | ExpandableComposite.EXPANDED);
202204
managedForm.addPart(runPropertiesPart);
203-
gd = new GridData(SWT.FILL, SWT.FILL, true, false);
204205
runPropertiesPart.getSection()
205-
.setLayoutData(gd);
206+
.setLayoutData(PageLayoutUtils.createExpanded());
207+
runPropertiesPart.getSection()
208+
.addExpansionListener(new ResizeExpansionAdapter(runPropertiesPart.getSection()));
206209

207210
// SECOND COLUMN
208211
if (supportsResolve) {

bndtools.core/src/bndtools/editor/project/AbstractRequirementListPart.java

Lines changed: 123 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,17 @@
44
import java.beans.PropertyChangeListener;
55
import java.io.File;
66
import java.util.ArrayList;
7+
import java.util.Arrays;
78
import java.util.Collections;
89
import java.util.EnumSet;
9-
import java.util.Iterator;
10+
import java.util.HashSet;
1011
import java.util.LinkedHashSet;
11-
import java.util.LinkedList;
1212
import java.util.List;
13+
import java.util.Map;
14+
import java.util.Objects;
1315
import java.util.Optional;
1416
import java.util.Set;
17+
import java.util.stream.Collectors;
1518

1619
import org.bndtools.core.ui.resource.RequirementLabelProvider;
1720
import org.bndtools.utils.dnd.AbstractViewerDropAdapter;
@@ -34,6 +37,7 @@
3437
import org.eclipse.jface.window.Window;
3538
import org.eclipse.jface.wizard.WizardDialog;
3639
import org.eclipse.swt.SWT;
40+
import org.eclipse.swt.custom.StyleRange;
3741
import org.eclipse.swt.events.KeyAdapter;
3842
import org.eclipse.swt.events.KeyEvent;
3943
import org.eclipse.swt.events.SelectionAdapter;
@@ -76,52 +80,76 @@ public AbstractRequirementListPart(Composite parent, FormToolkit toolkit, int st
7680
super(parent, toolkit, style);
7781
}
7882

79-
private final BndPreferences preferences = new BndPreferences();
80-
private final List<Requirement> requires = new ArrayList<>();
83+
private final BndPreferences preferences = new BndPreferences();
84+
/** Local requirements: written to this file's own merge key. */
85+
private final List<Requirement> requires = new ArrayList<>();
86+
/** Inherited requirements: from included files, shown in gray (read-only). */
87+
private final List<Requirement> inheritedRequires = new ArrayList<>();
8188

8289
private TableViewer viewer;
8390
private ToolItem addBundleTool;
8491
private ToolItem removeTool;
8592

86-
private boolean committing = false;
87-
private boolean inherited = false;
88-
private String inheritedProvenance = null;
93+
private boolean committing = false;
94+
/** Per-requirement provenance: maps each inherited requirement to the file path that defines it. */
95+
private Map<Requirement, String> inheritedProvenances = new java.util.LinkedHashMap<>();
96+
/** The key used to write local requirements (plain stem or stem.local). */
97+
private String localKey = null;
98+
/** Extra property key subscribed dynamically (the localKey when it's a suffix). */
99+
private String subscribedLocalKey = null;
89100

90101
/** Returns the primary bnd property key this part displays (e.g. {@code -runrequires}). */
91102
protected abstract String getPrimaryPropertyKey();
92103

93-
/** A label provider that renders items in grey to indicate they are inherited. */
94-
private static class InheritedRequirementLabelProvider extends RequirementLabelProvider {
104+
/** Returns the local write key (plain stem or stem.local) determined during the last refresh. */
105+
protected final String getLocalKey() {
106+
return localKey != null ? localKey : getPrimaryPropertyKey();
107+
}
108+
109+
/** Colors inherited items gray and local items with the default foreground. */
110+
private class MixedRequirementLabelProvider extends RequirementLabelProvider {
95111
private final Color grey;
96112

97-
InheritedRequirementLabelProvider(Display display) {
113+
MixedRequirementLabelProvider(Display display) {
98114
grey = display.getSystemColor(SWT.COLOR_DARK_GRAY);
99115
}
100116

101117
@Override
102118
public void update(ViewerCell cell) {
103119
super.update(cell);
104-
cell.setForeground(grey);
105-
// clear styled ranges so the grey foreground is not overridden
106-
cell.setStyleRanges(new org.eclipse.swt.custom.StyleRange[0]);
120+
if (inheritedRequires.contains(cell.getElement())) {
121+
cell.setForeground(grey);
122+
// clear styled ranges so the grey foreground is not overridden
123+
cell.setStyleRanges(new StyleRange[0]);
124+
}
107125
}
108126
}
109127

110128
protected TableViewer createViewer(Composite parent, FormToolkit tk) {
111129
Table table = tk.createTable(parent, SWT.FULL_SELECTION | SWT.MULTI | SWT.BORDER);
112130
viewer = new TableViewer(table);
113131
viewer.setContentProvider(ArrayContentProvider.getInstance());
114-
viewer.setLabelProvider(new RequirementLabelProvider());
132+
viewer.setLabelProvider(new MixedRequirementLabelProvider(table.getDisplay()));
115133

116134
// Listeners
117-
viewer.addSelectionChangedListener(event -> removeTool.setEnabled(!inherited && !viewer.getSelection()
118-
.isEmpty()));
135+
viewer.addSelectionChangedListener(event -> {
136+
IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
137+
boolean hasLocalSelected = !sel.isEmpty()
138+
&& sel.toList().stream().anyMatch(e -> requires.contains(e));
139+
removeTool.setEnabled(hasLocalSelected);
140+
});
119141
viewer.addDoubleClickListener(new IDoubleClickListener() {
120142
@Override
121143
public void doubleClick(DoubleClickEvent event) {
122-
if (inherited && inheritedProvenance != null) {
123-
openProvenanceFile(inheritedProvenance);
124-
}
144+
IStructuredSelection sel = (IStructuredSelection) viewer.getSelection();
145+
if (sel.isEmpty())
146+
return;
147+
Requirement req = (Requirement) sel.getFirstElement();
148+
if (!inheritedRequires.contains(req))
149+
return;
150+
String prov = inheritedProvenances.get(req);
151+
if (prov != null)
152+
openProvenanceFile(prov);
125153
}
126154
});
127155
table.addKeyListener(new KeyAdapter() {
@@ -237,14 +265,11 @@ private void doAddBundle() {
237265
private void doRemove() {
238266
IStructuredSelection selection = (IStructuredSelection) viewer.getSelection();
239267
if (!selection.isEmpty()) {
240-
Iterator<?> elements = selection.iterator();
241-
List<Object> removed = new LinkedList<>();
242-
while (elements.hasNext()) {
243-
Object element = elements.next();
244-
if (this.requires.remove(element))
245-
removed.add(element);
246-
}
247-
268+
// Only local items may be removed; inherited ones stay in their source file.
269+
@SuppressWarnings("unchecked")
270+
List<Object> removed = ((List<Object>) selection.toList()).stream()
271+
.filter(e -> this.requires.remove(e))
272+
.collect(Collectors.toList());
248273
if (!removed.isEmpty()) {
249274
viewer.remove(removed.toArray());
250275
markDirty();
@@ -264,44 +289,83 @@ public final void commitToModel(boolean onSave) {
264289

265290
@Override
266291
protected final void refreshFromModel() {
267-
List<Requirement> loadedReqs = doRefreshFromModel();
268-
if (loadedReqs == null)
269-
loadedReqs = Collections.emptyList();
292+
// Local requirements: only what is defined in this file's own merge keys.
293+
List<Requirement> newLocal = doRefreshFromModel();
294+
if (newLocal == null)
295+
newLocal = Collections.emptyList();
270296

271-
// Determine whether the property is inherited from another file.
272-
// Check stem and stem.* variant keys (e.g. -runrequires.shared).
297+
// Inherited requirements: merged view minus local.
273298
String primaryKey = getPrimaryPropertyKey();
274-
boolean nowInherited = primaryKey != null && model != null && !BndEditModelAccessor.hasLocalMergeProperty(model, primaryKey)
275-
&& !loadedReqs.isEmpty();
276-
Optional<String> provenance = (nowInherited && primaryKey != null && model != null)
277-
? BndEditModelAccessor.getPropertyProvenance(model, primaryKey)
278-
: Optional.empty();
279-
inherited = nowInherited;
280-
inheritedProvenance = provenance.orElse(null);
281-
282-
// Switch label provider based on inherited state; update tooltip
283-
if (inherited) {
284-
viewer.setLabelProvider(new InheritedRequirementLabelProvider(viewer.getControl()
285-
.getDisplay()));
286-
String tip = inheritedProvenance != null
287-
? "Inherited from " + inheritedProvenance + ". Double-click to open source."
288-
: "Inherited from an included file. Double-click to open source.";
289-
viewer.getControl()
290-
.setToolTipText(tip);
299+
List<Requirement> newInherited = Collections.emptyList();
300+
if (primaryKey != null && model != null) {
301+
List<Requirement> merged = BndEditModelAccessor.getMergedRequirements(model, primaryKey);
302+
if (merged != null && !merged.isEmpty()) {
303+
Set<Requirement> localSet = new HashSet<>(newLocal);
304+
newInherited = merged.stream()
305+
.filter(r -> !localSet.contains(r))
306+
.collect(Collectors.toList());
307+
}
308+
}
309+
310+
// Determine which key local additions should be written to.
311+
String newLocalKey = primaryKey;
312+
if (primaryKey != null && model != null) {
313+
String existingKey = BndEditModelAccessor.findLocalMergeKey(model, primaryKey);
314+
if (existingKey != null) {
315+
newLocalKey = existingKey;
316+
} else if (!newInherited.isEmpty()) {
317+
// No local key yet but inherited items exist: use a suffix so bnd merges them.
318+
newLocalKey = primaryKey + ".local";
319+
}
320+
}
321+
localKey = newLocalKey;
322+
323+
// Keep the property-change subscription aligned with the local key.
324+
if (!Objects.equals(subscribedLocalKey, newLocalKey)) {
325+
if (subscribedLocalKey != null && model != null)
326+
model.removePropertyChangeListener(subscribedLocalKey, this);
327+
subscribedLocalKey = newLocalKey;
328+
if (newLocalKey != null && model != null
329+
&& !Arrays.asList(getProperties()).contains(newLocalKey))
330+
model.addPropertyChangeListener(newLocalKey, this);
331+
}
332+
333+
// Update provenance tooltip for inherited items.
334+
if (!newInherited.isEmpty()) {
335+
inheritedProvenances = BndEditModelAccessor.getInheritedRequirementProvenances(model, primaryKey);
336+
String tip = inheritedProvenances.values().stream().findAny().isPresent()
337+
? "Some requirements are inherited from included files. Double-click an inherited item to open its source."
338+
: "Some requirements are inherited from included files.";
339+
viewer.getControl().setToolTipText(tip);
291340
} else {
292-
viewer.setLabelProvider(new RequirementLabelProvider());
293-
viewer.getControl()
294-
.setToolTipText(null);
341+
inheritedProvenances = Collections.emptyMap();
342+
viewer.getControl().setToolTipText(null);
295343
}
296-
addBundleTool.setEnabled(!inherited);
344+
345+
addBundleTool.setEnabled(true);
297346
removeTool.setEnabled(false);
298347

299-
if (loadedReqs.equals(this.requires))
348+
if (newInherited.equals(this.inheritedRequires) && newLocal.equals(this.requires))
300349
return;
301350

351+
this.inheritedRequires.clear();
352+
this.inheritedRequires.addAll(newInherited);
302353
this.requires.clear();
303-
this.requires.addAll(loadedReqs);
304-
viewer.setInput(this.requires);
354+
this.requires.addAll(newLocal);
355+
356+
List<Requirement> combined = new ArrayList<>(this.inheritedRequires.size() + this.requires.size());
357+
combined.addAll(this.inheritedRequires);
358+
combined.addAll(this.requires);
359+
viewer.setInput(combined);
360+
}
361+
362+
@Override
363+
public void dispose() {
364+
if (subscribedLocalKey != null && model != null) {
365+
model.removePropertyChangeListener(subscribedLocalKey, this);
366+
subscribedLocalKey = null;
367+
}
368+
super.dispose();
305369
}
306370

307371
@Override
@@ -336,13 +400,12 @@ private void openProvenanceFile(String absolutePath) {
336400
}
337401
}
338402

339-
/** Update the requirements already available with new ones. Already existing requirements will be removed. */
403+
/** Adds new requirements as local items. Items already in inherited or local lists are skipped. */
340404
private boolean updateViewerWithNewRequirements(Set<Requirement> adding) {
341-
// remove duplicates
405+
adding.removeAll(this.inheritedRequires);
342406
adding.removeAll(this.requires);
343-
if (adding.isEmpty()) {
407+
if (adding.isEmpty())
344408
return false;
345-
}
346409
this.requires.addAll(adding);
347410
viewer.add(adding.toArray());
348411
markDirty();

0 commit comments

Comments
 (0)