From 24b5f7be60a3a76aa0df73c098845e75596a8c36 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Sat, 14 Dec 2024 08:21:15 +0100 Subject: [PATCH 01/14] first draft of Bnd Effective Source tab Signed-off-by: Christoph Rueger add find text via CMD+F Signed-off-by: Christoph Rueger add Syntax highlighting via SourceViewer remove unnecessary code remove some code which came in from JarPrintPage.java Signed-off-by: Christoph Rueger fix resource bundle found in org.eclipse.ui.texteditor.EditorMessages.BUNDLE_FOR_CONSTRUCTED_KEYS which is unfortunatelly not accessible (private) Signed-off-by: Christoph Rueger add line numbers Signed-off-by: Christoph Rueger rename tab just "Effective" Signed-off-by: Christoph Rueger --- .../src/bndtools/editor/BndEditor.java | 23 +- .../editor/BndSourceEffectivePage.java | 200 ++++++++++++++++++ .../bndtools/core/ui/ExtendedFormEditor.java | 2 +- 3 files changed, 222 insertions(+), 3 deletions(-) create mode 100644 bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java diff --git a/bndtools.core/src/bndtools/editor/BndEditor.java b/bndtools.core/src/bndtools/editor/BndEditor.java index 2f20a08547..b03c872070 100644 --- a/bndtools.core/src/bndtools/editor/BndEditor.java +++ b/bndtools.core/src/bndtools/editor/BndEditor.java @@ -109,6 +109,7 @@ public class BndEditor extends ExtendedFormEditor implements IResourceChangeList public static final String WORKSPACE_EDITOR = "bndtools.bndWorkspaceConfigEditor"; public static final String SOURCE_PAGE = "__source_page"; + public static final String SOURCE_PAGE_EFFECTIVE = "__source_page_effective"; public static final String CONTENT_PAGE = "__content_page"; public static final String WORKSPACE_PAGE = "__workspace_page"; @@ -126,6 +127,7 @@ public class BndEditor extends ExtendedFormEditor implements IResourceChangeList private final static Image buildFileImg = Icons.image("icons/bndtools-logo-16x16.png"); private BndSourceEditorPage sourcePage; + private BndSourceEffectivePage sourcePageEffective; private Promise modelReady; private IResource inputResource; @@ -150,6 +152,7 @@ private void updateIncludedPages() { requiredPageIds.addAll(getPagesBnd(path)); } requiredPageIds.add(SOURCE_PAGE); + requiredPageIds.add(SOURCE_PAGE_EFFECTIVE); // Remove pages no longer required and remember the rest in a map int i = 0; @@ -167,8 +170,13 @@ private void updateIncludedPages() { // Cache new pages for (String pageId : requiredPageIds) { if (!pageCache.containsKey(pageId)) { - IFormPage page = SOURCE_PAGE.equals(pageId) ? sourcePage - : pageFactories.get(pageId) + IFormPage page; + if (SOURCE_PAGE.equals(pageId)) + page = sourcePage; + else if (SOURCE_PAGE_EFFECTIVE.equals(pageId)) + page = sourcePageEffective; + else + page = pageFactories.get(pageId) .createPage(this, model, pageId); pageCache.put(pageId, page); } @@ -184,6 +192,8 @@ private void updateIncludedPages() { if (existingPointer >= getPageCount()) { if (SOURCE_PAGE.equals(requiredId)) addPage(sourcePage, getEditorInput()); + else if (SOURCE_PAGE_EFFECTIVE.equals(requiredId)) + addPage(sourcePageEffective, getEditorInput()); else addPage(pageCache.get(requiredId)); } else { @@ -191,6 +201,8 @@ private void updateIncludedPages() { if (!requiredId.equals(existingPage.getId())) { if (SOURCE_PAGE.equals(requiredId)) addPage(existingPointer, sourcePage, getEditorInput()); + else if (SOURCE_PAGE_EFFECTIVE.equals(requiredId)) + addPage(existingPointer, sourcePageEffective, getEditorInput()); else addPage(existingPointer, pageCache.get(requiredId)); } @@ -204,6 +216,7 @@ private void updateIncludedPages() { // Set the source page title setPageText(sourcePage.getIndex(), "Source"); + setPageText(sourcePageEffective.getIndex(), "Effective"); } @@ -225,6 +238,7 @@ private static boolean isExtWorkspaceConfig(String path, String projectName) { private IHandlerActivation resolveHandlerActivation; private JobChangeAdapter resolveJobListener; + /* * (non-Javadoc) * @see bndtools.editor.IBndEditor#doSave(org.eclipse.core.runtime. @@ -255,6 +269,7 @@ public void doSave(IProgressMonitor monitor) { public void commitDirtyPages() { if (sourcePage.isActive() && sourcePage.isDirty()) { sourcePage.commit(true); + sourcePageEffective.setInput(this.model); } else { commitPages(true); sourcePage.refresh(); @@ -677,6 +692,7 @@ private Promise loadEditModel(File inputFile, BndEditModel model) thr private void initPages(IEditorSite site, IEditorInput input) throws PartInitException { // Initialise pages sourcePage = new BndSourceEditorPage(SOURCE_PAGE, this); + sourcePageEffective = new BndSourceEffectivePage(this, SOURCE_PAGE_EFFECTIVE, "Effective"); pageFactories.put(WORKSPACE_PAGE, WorkspacePage.MAIN_FACTORY); pageFactories.put(WORKSPACE_EXT_PAGE, WorkspacePage.EXT_FACTORY); pageFactories.put(CONTENT_PAGE, BundleContentPage.FACTORY); @@ -700,6 +716,8 @@ private void initPages(IEditorSite site, IEditorInput input) throws PartInitExce } sourcePage.init(site, input); sourcePage.initialize(this); + sourcePageEffective.init(site, input); + sourcePageEffective.initialize(this); } private void setPartNameForInput(IEditorInput input) { @@ -784,6 +802,7 @@ public void resourceChanged(IResourceChangeEvent event) { SWTConcurrencyUtil.execForDisplay(display, true, () -> { setPartNameForInput(newInput); sourcePage.setInput(newInput); + sourcePageEffective.setInput(this.model); }); } } else { diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java new file mode 100644 index 0000000000..fff4ce5106 --- /dev/null +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -0,0 +1,200 @@ +package bndtools.editor; + +import java.util.ResourceBundle; +import java.util.Set; + +import org.eclipse.core.commands.AbstractHandler; +import org.eclipse.core.commands.ExecutionEvent; +import org.eclipse.core.commands.ExecutionException; +import org.eclipse.core.commands.IHandler; +import org.eclipse.jdt.ui.JavaUI; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.text.Document; +import org.eclipse.jface.text.IFindReplaceTarget; +import org.eclipse.jface.text.TextViewer; +import org.eclipse.jface.text.source.CompositeRuler; +import org.eclipse.jface.text.source.LineNumberRulerColumn; +import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IWorkbenchPart; +import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.forms.IManagedForm; +import org.eclipse.ui.forms.editor.FormEditor; +import org.eclipse.ui.forms.editor.FormPage; +import org.eclipse.ui.forms.widgets.Form; +import org.eclipse.ui.forms.widgets.FormToolkit; +import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.texteditor.FindReplaceAction; + +import aQute.bnd.build.model.BndEditModel; +import aQute.bnd.exceptions.Exceptions; +import aQute.bnd.osgi.Processor; +import bndtools.editor.completion.BndSourceViewerConfiguration; + +/** + * The 'Effective source' tab in a Bnd Editor which renders an effective + * readonly view of all properties of the BndEditModel. This means that it shows + * all available properties which are pulled in via various include-mechanisms. + * This it is a useful debugging tool to make things visible to the human eye. + */ +public class BndSourceEffectivePage extends FormPage { + + + private final BndEditor bndEditor; + private BndEditModel editModel; + private SourceViewer viewer; + private StyledText styledText; + private boolean loading; + + public BndSourceEffectivePage(FormEditor formEditor, String id, String title) { + super(formEditor, id, title); + this.bndEditor = ((BndEditor) formEditor); + this.editModel = bndEditor.getModel(); + } + + @Override + protected void createFormContent(IManagedForm managedForm) { + FormToolkit toolkit = managedForm.getToolkit(); + ScrolledForm scrolledForm = managedForm.getForm(); + Form form = scrolledForm.getForm(); + toolkit.setBorderStyle(SWT.NULL); + + Composite body = form.getBody(); + body.setLayout(new FillLayout()); + + // ruler for line numbers + CompositeRuler ruler = new CompositeRuler(); + LineNumberRulerColumn ln = new LineNumberRulerColumn(); + ln.setForeground(Display.getCurrent() + .getSystemColor(SWT.COLOR_DARK_GRAY)); + ruler.addDecorator(0, ln); + + this.viewer = new SourceViewer(body, ruler, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL) { + @Override + protected boolean canPerformFind() { + return true; + } + }; + viewer.setDocument(new Document()); + viewer.configure(new BndSourceViewerConfiguration(bndEditor, JavaUI.getColorManager())); + styledText = viewer.getTextWidget(); + styledText.setEditable(false); + styledText.setFont(JFaceResources.getTextFont()); + + activateFindAndReplace(viewer, getSite(), this); + + + } + + /** + * Setup Eclipse's built in Search / Replace dialog. Also see + * {@link #getAdapter(Class)} + * + * @param textViewer + * @param site + * @param page + */ + private static void activateFindAndReplace(TextViewer textViewer, IWorkbenchPartSite site, + IWorkbenchPart page) { + FindReplaceAction findReplaceAction = new FindReplaceAction( + ResourceBundle.getBundle("org.eclipse.ui.texteditor.ConstructedEditorMessages"), "Editor.FindReplace.", + page); + IHandlerService handlerService = site.getService(IHandlerService.class); + IHandler handler = new AbstractHandler() { + + @Override + public Object execute(ExecutionEvent event) throws ExecutionException { + if (textViewer != null && textViewer.getDocument() != null) { + findReplaceAction.run(); + } + return null; + } + }; + + handlerService.activateHandler("org.eclipse.ui.edit.findReplace", handler); + } + + + @Override + public boolean isEditor() { + return true; + } + + @Override + public void setActive(boolean active) { + super.setActive(active); + + IActionBars actionBars = getEditorSite().getActionBars(); + + if (active) { + update(); + } else { + } + } + + private static String print(BndEditModel model) throws Exception { + if (model == null) { + return "..."; + } + + try { + StringBuilder sb = new StringBuilder(); + + Processor p = new BndEditModel(model, true).getProperties(); + Set propertyKeys = p.getPropertyKeys(true); + for (String k : propertyKeys) { + + String value = p.getProperty(k); + sb.append(k) + .append(": ") + .append(value) + .append("\n"); + } + + return sb.toString(); + } catch (Exception e) { + throw Exceptions.duck(e); + } + + } + + + private void update() { + if (loading || styledText == null || !isActive()) { + return; + } + loading = true; + try { + String text = print(editModel); + viewer.setDocument(new Document(text)); + styledText.setFocus(); + } catch (Exception e) { + throw Exceptions.duck(e); + } + + } + + public void setInput(BndEditModel model) { + this.editModel = model; + loading = false; + update(); + } + + @SuppressWarnings("unchecked") + @Override + public T getAdapter(Class adapter) { + if (IFindReplaceTarget.class.equals(adapter)) { + // needed for find/replace via CMD+F / Ctrl+F + return (T) viewer.getFindReplaceTarget(); + } + return super.getAdapter(adapter); + } + + +} diff --git a/bndtools.core/src/org/bndtools/core/ui/ExtendedFormEditor.java b/bndtools.core/src/org/bndtools/core/ui/ExtendedFormEditor.java index 86ea31dd11..27c5486c41 100644 --- a/bndtools.core/src/org/bndtools/core/ui/ExtendedFormEditor.java +++ b/bndtools.core/src/org/bndtools/core/ui/ExtendedFormEditor.java @@ -21,7 +21,6 @@ public abstract class ExtendedFormEditor extends FormEditor { private ITextEditor sourcePage; - private ImageDescriptor baseImageDescriptor; private Image titleImage; @@ -116,4 +115,5 @@ protected void setSourcePage(ITextEditor sourcePage) { this.sourcePage = sourcePage; } + } From 52553391cf4b3c26ec2e26a5f8e1e63adba980f5 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Thu, 19 Dec 2024 22:45:32 +0100 Subject: [PATCH 02/14] add tableviewer Signed-off-by: Christoph Rueger remove file --- .../editor/BndSourceEffectivePage.java | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index fff4ce5106..dbcd7d9532 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -15,11 +15,17 @@ import org.eclipse.jface.text.source.CompositeRuler; import org.eclipse.jface.text.source.LineNumberRulerColumn; import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.jface.viewers.ArrayContentProvider; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Table; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; @@ -29,6 +35,7 @@ import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; +import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.texteditor.FindReplaceAction; @@ -49,6 +56,7 @@ public class BndSourceEffectivePage extends FormPage { private final BndEditor bndEditor; private BndEditModel editModel; private SourceViewer viewer; + private TableViewer tableViewer; private StyledText styledText; private boolean loading; @@ -89,6 +97,29 @@ protected boolean canPerformFind() { activateFindAndReplace(viewer, getSite(), this); + // Table + Section section = managedForm.getToolkit() + .createSection(body, Section.TITLE_BAR | Section.EXPANDED); + section.setText("Sortable Table"); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Composite sectionClient = managedForm.getToolkit() + .createComposite(section, SWT.NONE); + sectionClient.setLayout(new org.eclipse.swt.layout.GridLayout(1, false)); + managedForm.getToolkit() + .paintBordersFor(sectionClient); + section.setClient(sectionClient); + + tableViewer = new TableViewer(sectionClient, + SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + Table table = tableViewer.getTable(); + table.setHeaderVisible(true); + table.setLinesVisible(true); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + createColumns(); + tableViewer.setContentProvider(ArrayContentProvider.getInstance()); + tableViewer.setInput(getTableData()); } @@ -196,5 +227,77 @@ public T getAdapter(Class adapter) { return super.getAdapter(adapter); } + // Table + private void createColumns() { + String[] titles = { + "key", "value" + }; + int[] bounds = { + 100, 100 + }; + + for (int i = 0; i < titles.length; i++) { + final TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); + column.getColumn() + .setText(titles[i]); + column.getColumn() + .setWidth(bounds[i]); + column.getColumn() + .setResizable(true); + column.getColumn() + .setMoveable(true); + + final int colNum = i; + column.setLabelProvider(new ColumnLabelProvider() { + @Override + public String getText(Object element) { + return String.valueOf(getColumnValue(element, colNum)); + } + }); + + final int columnIndex = i; + column.getColumn() + .addListener(SWT.Selection, e -> { + // Comparator comparator = Comparator.comparing(obj + // -> getColumnValue(obj, columnIndex)); + // tableViewer.setComparator(new + // ViewerComparator(comparator)); + }); + } + } + + private Object getColumnValue(Object element, int columnIndex) { + if (element instanceof String[]) { + String[] row = (String[]) element; + return row[columnIndex]; + } + return null; + } + + private Object[] getTableData() { + + try { + + Processor p = new BndEditModel(editModel, true).getProperties(); + Set propertyKeys = p.getPropertyKeys(true); + + Object[] result = new Object[propertyKeys.size()]; + int index = 0; + + for (String key : propertyKeys) { + String value = p.getProperty(key); + result[index] = new String[] { + key, value + }; + index++; + } + + return result; + + } catch (Exception e) { + throw Exceptions.duck(e); + } + + } } From 20369630fad0608a869095c220f86216695994db Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Fri, 20 Dec 2024 11:59:22 +0100 Subject: [PATCH 03/14] use resizable SashForm Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 45 +++++++++++++++++-- 1 file changed, 42 insertions(+), 3 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index dbcd7d9532..d0f09e2bf8 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -20,6 +20,7 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; import org.eclipse.swt.custom.StyledText; import org.eclipse.swt.layout.FillLayout; import org.eclipse.swt.layout.GridData; @@ -76,6 +77,34 @@ protected void createFormContent(IManagedForm managedForm) { Composite body = form.getBody(); body.setLayout(new FillLayout()); + // Create a SashForm for horizontal resizing + SashForm sashForm = new SashForm(body, SWT.HORIZONTAL); + sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + createSourceViewer(managedForm, sashForm); + createTableViewer(managedForm, sashForm); + + // Set the initial weights so each section takes half the space + sashForm.setWeights(new int[] { + 1, 1 + }); + + } + + private void createSourceViewer(IManagedForm managedForm, Composite body) { + + Section section = managedForm.getToolkit() + .createSection(body, Section.TITLE_BAR | Section.EXPANDED); + section.setText("Effective Source"); + section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Composite sectionClient = managedForm.getToolkit() + .createComposite(section, SWT.NONE); + sectionClient.setLayout(new org.eclipse.swt.layout.GridLayout(1, false)); + managedForm.getToolkit() + .paintBordersFor(sectionClient); + section.setClient(sectionClient); + // ruler for line numbers CompositeRuler ruler = new CompositeRuler(); LineNumberRulerColumn ln = new LineNumberRulerColumn(); @@ -83,13 +112,15 @@ protected void createFormContent(IManagedForm managedForm) { .getSystemColor(SWT.COLOR_DARK_GRAY)); ruler.addDecorator(0, ln); - this.viewer = new SourceViewer(body, ruler, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL) { + this.viewer = new SourceViewer(sectionClient, ruler, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL) { @Override protected boolean canPerformFind() { return true; } }; viewer.setDocument(new Document()); + viewer.getControl() + .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); viewer.configure(new BndSourceViewerConfiguration(bndEditor, JavaUI.getColorManager())); styledText = viewer.getTextWidget(); styledText.setEditable(false); @@ -97,7 +128,11 @@ protected boolean canPerformFind() { activateFindAndReplace(viewer, getSite(), this); - // Table + // Force layout to update + sectionClient.layout(true, true); + } + + private void createTableViewer(IManagedForm managedForm, Composite body) { Section section = managedForm.getToolkit() .createSection(body, Section.TITLE_BAR | Section.EXPANDED); section.setText("Sortable Table"); @@ -110,8 +145,9 @@ protected boolean canPerformFind() { .paintBordersFor(sectionClient); section.setClient(sectionClient); - tableViewer = new TableViewer(sectionClient, + this.tableViewer = new TableViewer(sectionClient, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + Table table = tableViewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); @@ -121,8 +157,11 @@ protected boolean canPerformFind() { tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(getTableData()); + // Force layout to update + sectionClient.layout(true, true); } + /** * Setup Eclipse's built in Search / Replace dialog. Also see * {@link #getAdapter(Class)} From 3be4ea2f66d519320e020339a8273ec61ba27c04 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Fri, 20 Dec 2024 12:41:54 +0100 Subject: [PATCH 04/14] show path column use resizable SashForm Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 31 +++++++++++++------ 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index d0f09e2bf8..5a5272bed2 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -1,5 +1,7 @@ package bndtools.editor; +import java.io.File; +import java.util.List; import java.util.ResourceBundle; import java.util.Set; @@ -43,6 +45,7 @@ import aQute.bnd.build.model.BndEditModel; import aQute.bnd.exceptions.Exceptions; import aQute.bnd.osgi.Processor; +import aQute.bnd.osgi.Processor.PropertyKey; import bndtools.editor.completion.BndSourceViewerConfiguration; /** @@ -71,6 +74,9 @@ public BndSourceEffectivePage(FormEditor formEditor, String id, String title) { protected void createFormContent(IManagedForm managedForm) { FormToolkit toolkit = managedForm.getToolkit(); ScrolledForm scrolledForm = managedForm.getForm(); + scrolledForm.setExpandHorizontal(true); + scrolledForm.setExpandVertical(true); + Form form = scrolledForm.getForm(); toolkit.setBorderStyle(SWT.NULL); @@ -128,8 +134,6 @@ protected boolean canPerformFind() { activateFindAndReplace(viewer, getSite(), this); - // Force layout to update - sectionClient.layout(true, true); } private void createTableViewer(IManagedForm managedForm, Composite body) { @@ -157,8 +161,6 @@ private void createTableViewer(IManagedForm managedForm, Composite body) { tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(getTableData()); - // Force layout to update - sectionClient.layout(true, true); } @@ -269,10 +271,10 @@ public T getAdapter(Class adapter) { // Table private void createColumns() { String[] titles = { - "key", "value" + "key", "value", "path" }; int[] bounds = { - 100, 100 + 100, 200, 200 }; for (int i = 0; i < titles.length; i++) { @@ -318,15 +320,26 @@ private Object[] getTableData() { try { Processor p = new BndEditModel(editModel, true).getProperties(); - Set propertyKeys = p.getPropertyKeys(true); + // Set propertyKeys = p.getPropertyKeys(true); + List propertyKeys = p.getPropertyKeys(k -> true); Object[] result = new Object[propertyKeys.size()]; int index = 0; - for (String key : propertyKeys) { + for (PropertyKey prop : propertyKeys) { + String key = prop.key(); String value = p.getProperty(key); + String path = ""; + if (!prop.isLocalTo(p)) { + File propertiesFile = prop.processor() + .getPropertiesFile(); + if (propertiesFile != null) { + path = propertiesFile.getAbsolutePath(); + + } + } result[index] = new String[] { - key, value + key, value, path }; index++; } From 5b288688d7a69c8a55325e310b6341a91ea58d87 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Fri, 20 Dec 2024 13:31:44 +0100 Subject: [PATCH 05/14] open BndEditor on DoubleClick for non-local processors Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 59 +++++++++++++++++-- 1 file changed, 55 insertions(+), 4 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index 5a5272bed2..155bfbd294 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -9,6 +9,10 @@ import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.core.commands.IHandler; +import org.eclipse.core.resources.IFile; +import org.eclipse.core.resources.IWorkspaceRoot; +import org.eclipse.core.resources.ResourcesPlugin; +import org.eclipse.core.runtime.Path; import org.eclipse.jdt.ui.JavaUI; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.Document; @@ -19,6 +23,9 @@ import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.viewers.ArrayContentProvider; import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; @@ -30,8 +37,11 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Table; import org.eclipse.ui.IActionBars; +import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IWorkbenchPart; import org.eclipse.ui.IWorkbenchPartSite; +import org.eclipse.ui.PartInitException; +import org.eclipse.ui.PlatformUI; import org.eclipse.ui.forms.IManagedForm; import org.eclipse.ui.forms.editor.FormEditor; import org.eclipse.ui.forms.editor.FormPage; @@ -40,6 +50,7 @@ import org.eclipse.ui.forms.widgets.ScrolledForm; import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.handlers.IHandlerService; +import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.FindReplaceAction; import aQute.bnd.build.model.BndEditModel; @@ -161,6 +172,42 @@ private void createTableViewer(IManagedForm managedForm, Composite body) { tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(getTableData()); + tableViewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + IStructuredSelection selection = tableViewer.getStructuredSelection(); + String[] selectedItem = (String[]) selection.getFirstElement(); + if (selectedItem != null) { + String fpath = selectedItem[2]; + if (fpath == null || fpath.isBlank()) { + return; + } + + // System.out.println("Double-clicked on: " + + // fpath.toString()); + IWorkspaceRoot root = ResourcesPlugin.getWorkspace() + .getRoot(); + + File file = new File(fpath); + org.eclipse.core.runtime.IPath path = new Path(file.getAbsolutePath()); + IFile iFile = root.getFileForLocation(path); + if (iFile == null) { + // File is not in the workspace. You cannot directly get + // an IFile for it. + } + IEditorInput input = new FileEditorInput(iFile); + try { + PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .openEditor(input, BndEditor.WORKSPACE_EDITOR); + } catch (PartInitException e) { + throw Exceptions.duck(e); + } + } + } + }); + } @@ -246,6 +293,7 @@ private void update() { String text = print(editModel); viewer.setDocument(new Document(text)); styledText.setFocus(); + tableViewer.setInput(getTableData()); } catch (Exception e) { throw Exceptions.duck(e); } @@ -274,7 +322,7 @@ private void createColumns() { "key", "value", "path" }; int[] bounds = { - 100, 200, 200 + 200, 300, 300 }; for (int i = 0; i < titles.length; i++) { @@ -330,12 +378,15 @@ private Object[] getTableData() { String key = prop.key(); String value = p.getProperty(key); String path = ""; - if (!prop.isLocalTo(p)) { + if (!prop.isLocalTo(editModel.getOwner())) { File propertiesFile = prop.processor() .getPropertiesFile(); if (propertiesFile != null) { - path = propertiesFile.getAbsolutePath(); - + path = propertiesFile.getPath(); + // .replaceAll(prop.processor() + // .getBase() + // .getPath(), "") + // .substring(1); } } result[index] = new String[] { From 9d576c3c86224f5d05b3bd7aa025dec60fe868ce Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Sat, 21 Dec 2024 19:32:54 +0100 Subject: [PATCH 06/14] allow toggle between source / tableviewer and finally fix the scrolling issues by borrowing StackLayout from JARPrintPage.java Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 121 +++++++++++------- 1 file changed, 74 insertions(+), 47 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index 155bfbd294..0f806b43ac 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -29,10 +29,14 @@ import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.custom.StyledText; -import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Table; @@ -48,7 +52,6 @@ import org.eclipse.ui.forms.widgets.Form; import org.eclipse.ui.forms.widgets.FormToolkit; import org.eclipse.ui.forms.widgets.ScrolledForm; -import org.eclipse.ui.forms.widgets.Section; import org.eclipse.ui.handlers.IHandlerService; import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.FindReplaceAction; @@ -70,9 +73,12 @@ public class BndSourceEffectivePage extends FormPage { private final BndEditor bndEditor; private BndEditModel editModel; - private SourceViewer viewer; + private SourceViewer sourceViewer; private TableViewer tableViewer; private StyledText styledText; + private Button toggleButton; + private Composite viewersComposite; + private StackLayout stackLayout; private boolean loading; public BndSourceEffectivePage(FormEditor formEditor, String id, String title) { @@ -88,39 +94,59 @@ protected void createFormContent(IManagedForm managedForm) { scrolledForm.setExpandHorizontal(true); scrolledForm.setExpandVertical(true); + Form form = scrolledForm.getForm(); toolkit.setBorderStyle(SWT.NULL); - Composite body = form.getBody(); - body.setLayout(new FillLayout()); + Composite body = scrolledForm.getBody(); + body.setLayout(new GridLayout(1, false)); - // Create a SashForm for horizontal resizing - SashForm sashForm = new SashForm(body, SWT.HORIZONTAL); - sashForm.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + // Create toggle button + toggleButton = toolkit.createButton(body, "Toggle View", SWT.PUSH); + toggleButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); - createSourceViewer(managedForm, sashForm); - createTableViewer(managedForm, sashForm); + // Create composite for viewers + viewersComposite = toolkit.createComposite(body); + viewersComposite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + stackLayout = new StackLayout() { + @Override + protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) { + Point size = super.computeSize(composite, wHint, hHint, flushCache); + // hack to make sure styledText never grows outside of body + size.y = SWT.DEFAULT; + return size; + } + }; + viewersComposite.setLayout(stackLayout); - // Set the initial weights so each section takes half the space - sashForm.setWeights(new int[] { - 1, 1 + createSourceViewer(managedForm, viewersComposite); + createTableViewer(managedForm, viewersComposite); + + // Set initial view + stackLayout.topControl = sourceViewer.getControl(); + + // Add toggle button listener + toggleButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + if (stackLayout.topControl == sourceViewer.getControl()) { + stackLayout.topControl = tableViewer.getControl(); + toggleButton.setText("Show Source"); + } else { + stackLayout.topControl = sourceViewer.getControl(); + toggleButton.setText("Show Table"); + } + viewersComposite.layout(); + } }); + // Initial layout update + viewersComposite.layout(); + scrolledForm.reflow(true); } private void createSourceViewer(IManagedForm managedForm, Composite body) { - Section section = managedForm.getToolkit() - .createSection(body, Section.TITLE_BAR | Section.EXPANDED); - section.setText("Effective Source"); - section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Composite sectionClient = managedForm.getToolkit() - .createComposite(section, SWT.NONE); - sectionClient.setLayout(new org.eclipse.swt.layout.GridLayout(1, false)); - managedForm.getToolkit() - .paintBordersFor(sectionClient); - section.setClient(sectionClient); // ruler for line numbers CompositeRuler ruler = new CompositeRuler(); @@ -129,38 +155,36 @@ private void createSourceViewer(IManagedForm managedForm, Composite body) { .getSystemColor(SWT.COLOR_DARK_GRAY)); ruler.addDecorator(0, ln); - this.viewer = new SourceViewer(sectionClient, ruler, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL) { + this.sourceViewer = new SourceViewer(body, ruler, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL) { @Override protected boolean canPerformFind() { return true; } }; - viewer.setDocument(new Document()); - viewer.getControl() - .setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - viewer.configure(new BndSourceViewerConfiguration(bndEditor, JavaUI.getColorManager())); - styledText = viewer.getTextWidget(); + + GridData sourceViewerLayoutData = new GridData(SWT.FILL, SWT.FILL, true, false); + sourceViewerLayoutData.heightHint = 100; // Set to 100 pixels high + + sourceViewer.setDocument(new Document()); + sourceViewer.getControl() + .setLayoutData(sourceViewerLayoutData); + sourceViewer.configure(new BndSourceViewerConfiguration(bndEditor, JavaUI.getColorManager())); + styledText = sourceViewer.getTextWidget(); styledText.setEditable(false); styledText.setFont(JFaceResources.getTextFont()); + styledText.setAlwaysShowScrollBars(true); + + activateFindAndReplace(sourceViewer, getSite(), this); - activateFindAndReplace(viewer, getSite(), this); + sourceViewer.getControl() + .getParent() + .layout(true, true); } private void createTableViewer(IManagedForm managedForm, Composite body) { - Section section = managedForm.getToolkit() - .createSection(body, Section.TITLE_BAR | Section.EXPANDED); - section.setText("Sortable Table"); - section.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Composite sectionClient = managedForm.getToolkit() - .createComposite(section, SWT.NONE); - sectionClient.setLayout(new org.eclipse.swt.layout.GridLayout(1, false)); - managedForm.getToolkit() - .paintBordersFor(sectionClient); - section.setClient(sectionClient); - - this.tableViewer = new TableViewer(sectionClient, + + this.tableViewer = new TableViewer(body, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); Table table = tableViewer.getTable(); @@ -208,6 +232,9 @@ public void doubleClick(DoubleClickEvent event) { } }); + tableViewer.getControl() + .getParent() + .layout(true, true); } @@ -291,7 +318,7 @@ private void update() { loading = true; try { String text = print(editModel); - viewer.setDocument(new Document(text)); + sourceViewer.setDocument(new Document(text)); styledText.setFocus(); tableViewer.setInput(getTableData()); } catch (Exception e) { @@ -311,7 +338,7 @@ public void setInput(BndEditModel model) { public T getAdapter(Class adapter) { if (IFindReplaceTarget.class.equals(adapter)) { // needed for find/replace via CMD+F / Ctrl+F - return (T) viewer.getFindReplaceTarget(); + return (T) sourceViewer.getFindReplaceTarget(); } return super.getAdapter(adapter); } From 43af2ceb20e3a16fa5d99401d1495efe9b2cf4c5 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Sun, 22 Dec 2024 23:25:40 +0100 Subject: [PATCH 07/14] avoid duplicates in tableviewer Signed-off-by: Christoph Rueger --- .../src/bndtools/editor/BndSourceEffectivePage.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index 0f806b43ac..fc8b252727 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -395,13 +395,17 @@ private Object[] getTableData() { try { Processor p = new BndEditModel(editModel, true).getProperties(); - // Set propertyKeys = p.getPropertyKeys(true); List propertyKeys = p.getPropertyKeys(k -> true); - Object[] result = new Object[propertyKeys.size()]; + // avoid duplicates because Project is parent of bnd.bnd and also + // gets the same properties + // but with higher floor + List visible = PropertyKey.findVisible(propertyKeys); + + Object[] result = new Object[visible.size()]; int index = 0; - for (PropertyKey prop : propertyKeys) { + for (PropertyKey prop : visible) { String key = prop.key(); String value = p.getProperty(key); String path = ""; From 65239b7015ff1f13a02b0a125c8c143f148ed72f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Sun, 22 Dec 2024 23:29:15 +0100 Subject: [PATCH 08/14] show tableViewer first Signed-off-by: Christoph Rueger --- .../src/bndtools/editor/BndSourceEffectivePage.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index fc8b252727..d96f837cf5 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -102,7 +102,7 @@ protected void createFormContent(IManagedForm managedForm) { body.setLayout(new GridLayout(1, false)); // Create toggle button - toggleButton = toolkit.createButton(body, "Toggle View", SWT.PUSH); + toggleButton = toolkit.createButton(body, "Show as Source", SWT.PUSH); toggleButton.setLayoutData(new GridData(SWT.FILL, SWT.TOP, true, false)); // Create composite for viewers @@ -123,7 +123,7 @@ protected Point computeSize(Composite composite, int wHint, int hHint, boolean f createTableViewer(managedForm, viewersComposite); // Set initial view - stackLayout.topControl = sourceViewer.getControl(); + stackLayout.topControl = tableViewer.getControl(); // Add toggle button listener toggleButton.addSelectionListener(new SelectionAdapter() { @@ -131,10 +131,10 @@ protected Point computeSize(Composite composite, int wHint, int hHint, boolean f public void widgetSelected(SelectionEvent e) { if (stackLayout.topControl == sourceViewer.getControl()) { stackLayout.topControl = tableViewer.getControl(); - toggleButton.setText("Show Source"); + toggleButton.setText("Show as Source"); } else { stackLayout.topControl = sourceViewer.getControl(); - toggleButton.setText("Show Table"); + toggleButton.setText("Show as Table"); } viewersComposite.layout(); } From 1ac07cc6054e4be16c69ff5dc53aa0f5a873e689 Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 23 Dec 2024 10:25:58 +0100 Subject: [PATCH 09/14] store PropertyKey in table data this gives more flexibility when working with the values since we just don't have strings only Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 80 ++++++++++++------- 1 file changed, 53 insertions(+), 27 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index d96f837cf5..9b82aa13c1 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -200,15 +200,14 @@ private void createTableViewer(IManagedForm managedForm, Composite body) { @Override public void doubleClick(DoubleClickEvent event) { IStructuredSelection selection = tableViewer.getStructuredSelection(); - String[] selectedItem = (String[]) selection.getFirstElement(); - if (selectedItem != null) { - String fpath = selectedItem[2]; + Object firstElement = selection.getFirstElement(); + + if(firstElement instanceof PropertyKey prop) { + String fpath = getPropertyKeyPath(prop); if (fpath == null || fpath.isBlank()) { return; } - // System.out.println("Double-clicked on: " + - // fpath.toString()); IWorkspaceRoot root = ResourcesPlugin.getWorkspace() .getRoot(); @@ -367,7 +366,7 @@ private void createColumns() { column.setLabelProvider(new ColumnLabelProvider() { @Override public String getText(Object element) { - return String.valueOf(getColumnValue(element, colNum)); + return String.valueOf(getColumnContent(element, titles[colNum])); } }); @@ -382,14 +381,57 @@ public String getText(Object element) { } } - private Object getColumnValue(Object element, int columnIndex) { - if (element instanceof String[]) { - String[] row = (String[]) element; - return row[columnIndex]; + private Object getColumnContent(Object element, String colName) { + if (element instanceof PropertyKey prop) { + switch (colName) { + case "key" : return prop.key(); + case "value" : + return prop.processor() + .getProperty(prop.key()); + case "path" : { + String path = getPropertyKeyPath(prop); + if (path.isBlank()) { + return path; + } + + if (path.equals(prop.processor() + .getBase() + .getPath())) { + return "-same-"; + } + + // cut the beginning to get only e.g. /cnf/build.bnd instead + // of absolute path + return path.replaceAll(prop.processor() + .getBase() + .getPath(), "") + .substring(1); + } + + default : + throw new IllegalArgumentException("Unknown column: " + colName); + } + } return null; } + private String getPropertyKeyPath(PropertyKey prop) { + String path = ""; + if (!prop.isLocalTo(editModel.getOwner())) { + File propertiesFile = prop.processor() + .getPropertiesFile(); + if (propertiesFile != null) { + path = propertiesFile.getPath(); + // .replaceAll(prop.processor() + // .getBase() + // .getPath(), "") + // .substring(1); + } + } + return path; + } + private Object[] getTableData() { try { @@ -406,23 +448,7 @@ private Object[] getTableData() { int index = 0; for (PropertyKey prop : visible) { - String key = prop.key(); - String value = p.getProperty(key); - String path = ""; - if (!prop.isLocalTo(editModel.getOwner())) { - File propertiesFile = prop.processor() - .getPropertiesFile(); - if (propertiesFile != null) { - path = propertiesFile.getPath(); - // .replaceAll(prop.processor() - // .getBase() - // .getPath(), "") - // .substring(1); - } - } - result[index] = new String[] { - key, value, path - }; + result[index] = prop; index++; } From 628669717ffd8b1095fbd5d897efdad783d5931f Mon Sep 17 00:00:00 2001 From: Christoph Rueger Date: Mon, 23 Dec 2024 10:30:39 +0100 Subject: [PATCH 10/14] improve display of path Signed-off-by: Christoph Rueger --- .../editor/BndSourceEffectivePage.java | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index 9b82aa13c1..0fb75716ed 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -394,12 +394,6 @@ private Object getColumnContent(Object element, String colName) { return path; } - if (path.equals(prop.processor() - .getBase() - .getPath())) { - return "-same-"; - } - // cut the beginning to get only e.g. /cnf/build.bnd instead // of absolute path return path.replaceAll(prop.processor() @@ -418,16 +412,11 @@ private Object getColumnContent(Object element, String colName) { private String getPropertyKeyPath(PropertyKey prop) { String path = ""; - if (!prop.isLocalTo(editModel.getOwner())) { - File propertiesFile = prop.processor() - .getPropertiesFile(); - if (propertiesFile != null) { - path = propertiesFile.getPath(); - // .replaceAll(prop.processor() - // .getBase() - // .getPath(), "") - // .substring(1); - } + + File propertiesFile = prop.processor() + .getPropertiesFile(); + if (propertiesFile != null) { + path = propertiesFile.getPath(); } return path; } From b86267ed25202577be66e39e9ebcfadfe59b661e Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Jan 2025 16:04:31 +0100 Subject: [PATCH 11/14] Added key provenances to the UTF8Properties --- Signed-off-by: github-actions Signed-off-by: github-actions --- .../lib/utf8properties/PropertiesParser.java | 30 +++--- .../lib/utf8properties/UTF8Properties.java | 100 +++++++++++++++++- .../lib/utf8properties/package-info.java | 2 +- .../utf8properties/UTF8PropertiesTest.java | 35 ++++++ 4 files changed, 148 insertions(+), 19 deletions(-) diff --git a/aQute.libg/src/aQute/lib/utf8properties/PropertiesParser.java b/aQute.libg/src/aQute/lib/utf8properties/PropertiesParser.java index 3fffdd7ba0..f561004bd1 100644 --- a/aQute.libg/src/aQute/lib/utf8properties/PropertiesParser.java +++ b/aQute.libg/src/aQute/lib/utf8properties/PropertiesParser.java @@ -2,7 +2,6 @@ import java.util.Collection; import java.util.Collections; -import java.util.Properties; import aQute.lib.hex.Hex; import aQute.lib.strings.Strings; @@ -40,18 +39,20 @@ final class PropertiesParser { INFO['\\'] = NOKEY; } - private int n = 0; - private int line = 0; - private int pos = -1; - private int marker = 0; - private char current; - private Properties properties; - private boolean validKey; - private boolean continuation = true; + private int n = 0; + private int line = 0; + private int pos = -1; + private int marker = 0; + private char current; + private UTF8Properties properties; + private boolean validKey; + private boolean continuation = true; private final Collection syntaxHeaders; + private final String provenance; - PropertiesParser(String source, String file, Reporter reporter, Properties properties, - Collection syntaxHeaders) { + PropertiesParser(String source, String file, Reporter reporter, UTF8Properties properties, + Collection syntaxHeaders, String provenance) { + this.provenance = provenance; this.source = source.toCharArray(); this.file = file; this.reporter = reporter; @@ -154,7 +155,7 @@ void parse() { next(); skipWhitespace(); if (current == '\n') { - properties.put(key, ""); + properties.setProperty(key, "", provenance); continue; } } @@ -162,11 +163,10 @@ void parse() { if (current != '\n') { String value = token(LINE, isSyntaxHeader(key)); - properties.put(key, value); - + properties.setProperty(key, value, provenance); } else { error("No value specified for key: %s. An empty value should be specified as '%> decoders = Collections.unmodifiableList( Arrays.asList(ThreadLocal.withInitial(UTF_8::newDecoder), ThreadLocal.withInitial(ISO_8859_1::newDecoder))); + record Provenance(String source) {} + + private final Map provenance = new HashMap<>(); + public UTF8Properties(Properties p) { super(p); } @@ -66,7 +73,8 @@ public UTF8Properties(File file, Reporter reporter) throws Exception { load(file, reporter, (Collection) null); } - public UTF8Properties() {} + public UTF8Properties() { + } private static Collection fromArray(String[] array) { return (array != null) ? Arrays.asList(array) : null; @@ -95,8 +103,13 @@ public void load(String source, File file, Reporter reporter, String[] syntaxHea } public void load(String source, File file, Reporter reporter, Collection syntaxHeaders) throws IOException { + load(source, file, reporter, syntaxHeaders, file == null ? "" : file.getAbsolutePath()); + } + + public void load(String source, File file, Reporter reporter, Collection syntaxHeaders, String provenance) + throws IOException { PropertiesParser parser = new PropertiesParser(source, file == null ? null : file.getAbsolutePath(), reporter, - this, syntaxHeaders); + this, syntaxHeaders, provenance); parser.parse(); } @@ -201,7 +214,7 @@ private UTF8Properties replaceAll(Pattern regex, String replacement) { String value = (String) entry.getValue(); value = regex.matcher(value) .replaceAll(replacement); - result.put(key, value); + result.setProperty(key, value, getProvenance(key).orElse(null)); } return result; } @@ -221,4 +234,85 @@ public UTF8Properties replaceHere(File file) { } return replaceAll(HERE_PATTERN, Matcher.quoteReplacement(here)); } + + public synchronized Object setProperty(String key, String value, String provenance) { + if (provenance != null) + getProvenance().put(key, new Provenance(provenance)); + return super.setProperty(key, value); + } + + @Override + public synchronized Object remove(Object key) { + getProvenance().remove(key); + return super.remove(key); + } + + /** + * Get the provenance of the given key if set + * + * @param key the key + */ + + public Optional getProvenance(String key) { + Provenance provenance = getProvenance().get(key); + return Optional.ofNullable(provenance) + .map(Provenance::source); + } + + /** + * Set the provenance of the given key + * + * @param key the key + * @param provenance the provenance, maybe null to remove + */ + + public UTF8Properties setProvenance(String key, String provenance) { + if (provenance == null) + getProvenance().remove(key); + else + getProvenance().put(key, new Provenance(provenance)); + return this; + } + + /** + * Load the properties from a properties. If the properties is a + * UTF8Properties, we also copy the provenance. + * + * @param properties the properties + * @param overwriteIfPresent overwrite an exissting value in this properties + */ + public void load(Properties properties, boolean overwriteIfPresent) { + BiConsumer set = properties instanceof UTF8Properties p + ? (k, v) -> setProperty(k, v, p.getProvenance(k) + .orElse(null)) + : (k, v) -> setProperty(k, v); + + properties.forEach((k, v) -> { + String key = (String) k; + String value = (String) v; + if (overwriteIfPresent || !contains(key)) + set.accept(key, value); + }); + } + + Map getProvenance() { + return provenance; + } + + @Override + public synchronized void putAll(Map t) { + if (t instanceof Properties p) { + load(p, true); + } else + super.putAll(t); + } + + /** + * Set the provenance on all current keys + * + * @param provenance + */ + public void setProvenance(String provenance) { + keySet().forEach(k -> setProvenance((String) k, provenance)); + } } diff --git a/aQute.libg/src/aQute/lib/utf8properties/package-info.java b/aQute.libg/src/aQute/lib/utf8properties/package-info.java index 4f29db7f04..7b3f44acae 100644 --- a/aQute.libg/src/aQute/lib/utf8properties/package-info.java +++ b/aQute.libg/src/aQute/lib/utf8properties/package-info.java @@ -1,4 +1,4 @@ -@Version("4.1.0") +@Version("4.2.0") package aQute.lib.utf8properties; import org.osgi.annotation.versioning.Version; diff --git a/aQute.libg/test/aQute/lib/utf8properties/UTF8PropertiesTest.java b/aQute.libg/test/aQute/lib/utf8properties/UTF8PropertiesTest.java index 2adab324c6..5ff3d6d4c3 100644 --- a/aQute.libg/test/aQute/lib/utf8properties/UTF8PropertiesTest.java +++ b/aQute.libg/test/aQute/lib/utf8properties/UTF8PropertiesTest.java @@ -405,6 +405,41 @@ public void testWriteFile(@InjectTemporaryDirectory assertThat(p1).containsExactlyInAnyOrderEntriesOf(p); } + @Test + public void testProvenance() throws IOException { + UTF8Properties a = new UTF8Properties(); + a.load(""" + a.a = 1 + a.b = 2 + x = 0 + """, null, null, null, "from_a"); + UTF8Properties b = new UTF8Properties(); + b.load(""" + b.a = 1 + b.c = 3 + x = 0 + """, null, null, null, "from_b"); + + assertThat(a.getProvenance("x")).isPresent() + .get() + .isEqualTo("from_a"); + assertThat(b.getProvenance("x")).isPresent() + .get() + .isEqualTo("from_b"); + assertThat(a.getProvenance("y")).isNotPresent(); + + a.load(b, true); + assertThat(a.getProvenance("x")).isPresent() + .get() + .isEqualTo("from_b"); + assertThat(a.getProvenance("a.a")).isPresent() + .get() + .isEqualTo("from_a"); + assertThat(a.getProvenance("b.a")).isPresent() + .get() + .isEqualTo("from_b"); + } + private void testProperty(String content, String key, String value) throws IOException { testProperty(content, key, value, null); } From a198f49d3ebc7cf0070844effd2135b0bf3bba1b Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Jan 2025 16:08:45 +0100 Subject: [PATCH 12/14] Leverage the provenance facility in processor. All the different places where we set properties now mark their provenance. In the case of the original we mark the original propertiesFile, in case of includes we track the included file. For defaults we use the provenance as a comment --- Signed-off-by: github-actions Signed-off-by: github-actions --- .../test/test/ProcessorTest.java | 76 +++++++++++++++++++ .../testresources/provenance/bnd.bnd | 2 + .../src/aQute/bnd/build/MagicBnd.java | 9 ++- .../src/aQute/bnd/build/Project.java | 2 +- .../src/aQute/bnd/build/Workspace.java | 14 +++- .../src/aQute/bnd/build/package-info.java | 2 +- .../src/aQute/bnd/maven/package-info.java | 2 +- .../src/aQute/bnd/osgi/Processor.java | 70 +++++++++++++++-- .../src/aQute/bnd/osgi/package-info.java | 2 +- .../bnd/osgi/repository/package-info.java | 2 +- .../src/aQute/bnd/print/package-info.java | 2 +- .../bnd/service/generate/package-info.java | 2 +- 12 files changed, 165 insertions(+), 20 deletions(-) create mode 100644 biz.aQute.bndlib.tests/testresources/provenance/bnd.bnd diff --git a/biz.aQute.bndlib.tests/test/test/ProcessorTest.java b/biz.aQute.bndlib.tests/test/test/ProcessorTest.java index 02b2adac2a..155f0355dc 100644 --- a/biz.aQute.bndlib.tests/test/test/ProcessorTest.java +++ b/biz.aQute.bndlib.tests/test/test/ProcessorTest.java @@ -32,6 +32,7 @@ import aQute.lib.collections.ExtList; import aQute.lib.io.IO; import aQute.lib.strings.Strings; +import aQute.lib.utf8properties.UTF8Properties; import aQute.libg.reporter.ReporterAdapter; import aQute.service.reporter.Reporter; import aQute.service.reporter.Reporter.SetLocation; @@ -774,4 +775,79 @@ public void testIncludeItself() throws IOException { } } + @Test + public void testProvenance() throws IOException { + File base = IO.getFile("generated/provenance"); + try { + base.mkdirs(); + File bnd = new File(base, "bnd.bnd"); + IO.store(""" + -include sup.bnd, ~inf.bnd + in_top = top + in_sup = top + #in_inf = top + top = true + """, bnd); + File sup = new File(base, "sup.bnd"); + IO.store(""" + #in_top = sup + in_sup = sup + #in_inf = sup + sup = true + """, sup); + File inf = new File(base, "inf.bnd"); + IO.store(""" + -include sup.bnd, ~inf.bnd + in_top = inf + in_sup = inf + in_inf = inf + inf = true + sup = false + top = false + """, inf); + + try (Processor a = new Processor(); Processor b = new Processor(a)) { + a.setProperty("a", "true"); + b.setProperties(bnd); + UTF8Properties bp = (UTF8Properties) b.getProperties(); + assertThat(b.getProperty("a")).isEqualTo("true"); + assertThat(b.getProperty("in_top")).isEqualTo("top"); + assertThat(b.getProperty("in_sup")).isEqualTo("sup"); + assertThat(b.getProperty("in_inf")).isEqualTo("inf"); + assertThat(b.getProperty("top")).isEqualTo("true"); + assertThat(b.getProperty("sup")).isEqualTo("true"); + assertThat(b.getProperty("inf")).isEqualTo("true"); + + assertThat(bp.getProvenance("in_top")).isPresent() + .get() + .isEqualTo(bnd.getAbsolutePath()); + assertThat(bp.getProvenance("in_sup")).isPresent() + .get() + .isEqualTo(sup.getAbsolutePath()); + assertThat(bp.getProvenance("in_inf")).isPresent() + .get() + .isEqualTo(inf.getAbsolutePath()); + assertThat(bp.getProvenance("top")).isPresent() + .get() + .isEqualTo(bnd.getAbsolutePath()); + assertThat(bp.getProvenance("sup")).isPresent() + .get() + .isEqualTo(sup.getAbsolutePath()); + + bp.remove("in_top"); + assertThat(bp.getProvenance("in_top")).isNotPresent(); + + b.setProperty("in_top", "foo"); + assertThat(bp.getProvenance("in_top")).isNotPresent(); + b.setProperty("in_top", "bar", "xxx"); + assertThat(bp.getProvenance("in_top")).isPresent() + .get() + .isEqualTo("xxx"); + } + } finally { + IO.delete(base); + } + + } + } diff --git a/biz.aQute.bndlib.tests/testresources/provenance/bnd.bnd b/biz.aQute.bndlib.tests/testresources/provenance/bnd.bnd new file mode 100644 index 0000000000..b0f5609ab9 --- /dev/null +++ b/biz.aQute.bndlib.tests/testresources/provenance/bnd.bnd @@ -0,0 +1,2 @@ +-include a/sup.bnd,~a/inf.bnd + diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/MagicBnd.java b/biz.aQute.bndlib/src/aQute/bnd/build/MagicBnd.java index ec594e6309..8da1908eaa 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/MagicBnd.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/MagicBnd.java @@ -25,6 +25,7 @@ import aQute.bnd.result.Result; import aQute.lib.io.IO; import aQute.lib.strings.Strings; +import aQute.lib.utf8properties.UTF8Properties; import aQute.libg.re.Catalog; import aQute.libg.re.RE; import aQute.libg.re.RE.Match; @@ -77,8 +78,8 @@ private static Result convertOBR(Workspace workspace, File file) { .append("'"); } - Properties p = new Properties(); - p.put("-plugin.ext." + file.getName(), sb.toString()); + UTF8Properties p = new UTF8Properties(); + p.setProperty("-plugin.ext." + file.getName(), sb.toString(), file.getAbsolutePath()); return Result.ok(p); } @@ -163,8 +164,8 @@ private static Result convertMaven(Workspace ws, File file) { .collect(Collectors.joining())) .append("'"); - Properties p = new Properties(); - p.put("-plugin.ext." + file.getName(), sb.toString()); + UTF8Properties p = new UTF8Properties(); + p.setProperty("-plugin.ext." + file.getName(), sb.toString(), file.getAbsolutePath()); return Result.ok(p); } } diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/Project.java b/biz.aQute.bndlib/src/aQute/bnd/build/Project.java index 3e9fef260f..424dca797f 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/Project.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/Project.java @@ -324,7 +324,7 @@ public void prepare() throws Exception { // We use a builder to construct all the properties for // use. - setProperty("basedir", basePath); + setProperty("basedir", basePath, "[project]"); // If a bnd.bnd file exists, we read it. // Otherwise, we just do the build properties. diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/Workspace.java b/biz.aQute.bndlib/src/aQute/bnd/build/Workspace.java index 63820ffe13..1e8a2efaef 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/Workspace.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/Workspace.java @@ -178,6 +178,7 @@ public void close() { try (InputStream propStream = Workspace.class.getResourceAsStream("defaults.bnd")) { if (propStream != null) { props.load(propStream); + props.setProvenance("[defaults.bnd]"); } else { System.err.println("Cannot load defaults"); } @@ -393,7 +394,11 @@ private void fixupVersionDefaults() throws IOException { assert url != null : "We must have a specific defaults resource"; } try (InputStream in = url.openStream()) { - props.load(in); + if (props instanceof UTF8Properties utf8) { + String source = IO.collect(in); + utf8.load(source, null, null, null, "[version-defaults]"); + } else + props.load(in); } } @@ -1387,7 +1392,12 @@ public static Workspace createStandaloneWorkspace(Processor run, URI base) throw } Properties wsProperties = ws.getProperties(); runProperties.filterKey(k -> !k.startsWith(PLUGIN_STANDALONE)) - .forEachOrdered(wsProperties::put); + .forEachOrdered((k, v) -> { + if (wsProperties instanceof UTF8Properties utf8) { + utf8.setProperty(k, (String) v, "[standalone]"); + } else + wsProperties.put(k, v); + }); ws.fixupVersionDefaults(); ws.open(); diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java index b1e27609c8..4bbff519fb 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/package-info.java @@ -1,6 +1,6 @@ /** */ -@Version("4.6.0") +@Version("4.7.0") package aQute.bnd.build; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/maven/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/maven/package-info.java index 5890fdc768..06f5cb7b3d 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/maven/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/maven/package-info.java @@ -1,6 +1,6 @@ /** */ -@Version("3.5.0") +@Version("3.6.0") package aQute.bnd.maven; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java index e91613a544..2f5a0f03ab 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Processor.java @@ -35,6 +35,7 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Objects; +import java.util.Optional; import java.util.Properties; import java.util.Random; import java.util.Set; @@ -47,6 +48,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.BiConsumer; import java.util.function.BiFunction; import java.util.function.Consumer; import java.util.function.Predicate; @@ -805,21 +807,43 @@ public void doIncludeFile(File file, boolean overwrite, Properties target, Strin Properties sub = magicBnd(file); doIncludes(file.getParentFile(), sub); + + BiConsumer set = getSetterWithProvenance(file, target, sub); + // take care regarding overwriting properties for (Map.Entry entry : sub.entrySet()) { String key = (String) entry.getKey(); String value = (String) entry.getValue(); if (overwrite || !target.containsKey(key)) { - target.setProperty(key, value); + set.accept(key, value); } else if (extensionName != null) { String extensionKey = key + "." + extensionName; if (!target.containsKey(extensionKey)) - target.setProperty(extensionKey, value); + set.accept(extensionKey, value); } } } + private BiConsumer getSetterWithProvenance(File file, Properties target, Properties sub) { + int n = target instanceof UTF8Properties ? 1 : 0; + n += sub instanceof UTF8Properties ? 2 : 0; + BiConsumer set = switch (n) { + case 1 -> { + UTF8Properties t = (UTF8Properties) target; + yield (k, v) -> t.setProperty(k, v, file.getAbsolutePath()); + } + case 3 -> { + UTF8Properties t = (UTF8Properties) target; + UTF8Properties s = (UTF8Properties) sub; + yield (k, v) -> t.setProperty(k, v, s.getProvenance(k) + .orElse(file.getAbsolutePath())); + } + default -> (k, v) -> target.setProperty(k, v); + }; + return set; + } + /** * This method allows a sub Processor to override recognized included files. * In general we treat files as bnd files but a sub processor can override @@ -835,7 +859,7 @@ public void doIncludeFile(File file, boolean overwrite, Properties target, Strin protected Properties magicBnd(File file) throws IOException { if (Strings.endsWithIgnoreCase(file.getName(), ".mf")) { try (InputStream in = IO.stream(file)) { - return getManifestAsProperties(in); + return getManifestAsProperties(in, file.getAbsolutePath()); } } else return loadProperties(file); @@ -1022,7 +1046,8 @@ private String getProperty(String key, String deflt, String separator, boolean i * floor indicates where the property is defined relative to its parents. * Zero is in the current processor, 1, is its parents, and so on. */ - public record PropertyKey(Processor processor, String key, int floor) implements Comparable { + public record PropertyKey(Processor processor, String key, int floor) + implements Comparable { /** * Check if this PropertyKey belongs to the given processor @@ -1043,6 +1068,17 @@ public String getValue() { return processor.getProperty(key); } + /** + * Return the provenance of this key. This is generally the absolute + * path to the source file but can be a logical name as well. + */ + public Optional getProvenance() { + Properties properties = processor.getProperties(); + if (properties != null && properties instanceof UTF8Properties p) { + return p.getProvenance(key); + } else + return Optional.empty(); + } /** * Get the raw value of the property key * @@ -1361,25 +1397,45 @@ public void setProperty(String key, String value) { getProperties().put(normalizeKey(key), value); } + /** + * Add or overwrite a new property. + * + * @param key + * @param value + */ + public void setProperty(String key, String value, String provenance) { + Properties properties2 = getProperties(); + if (properties2 instanceof UTF8Properties utf8p) { + utf8p.setProperty(key, value, provenance); + } else + properties2.setProperty(key, value); + } + /** * Read a manifest but return a properties object. * * @param in * @throws IOException */ - public static Properties getManifestAsProperties(InputStream in) throws IOException { - Properties p = new UTF8Properties(); + public static Properties getManifestAsProperties(InputStream in, String provenance) throws IOException { + UTF8Properties p = new UTF8Properties(); Manifest manifest = new Manifest(in); for (Object object : manifest.getMainAttributes() .keySet()) { Attributes.Name key = (Attributes.Name) object; String value = manifest.getMainAttributes() .getValue(key); - p.put(key.toString(), value); + p.setProperty(key.toString(), value, provenance); } return p; } + // {@linkplain #getManifestAsProperties(InputStream, String)} + @Deprecated() + public static Properties getManifestAsProperties(InputStream in) throws IOException { + return getManifestAsProperties(in, null); + } + public File getPropertiesFile() { return propertiesFile; } diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/package-info.java index 4b7d3f6767..54fb151fbf 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/package-info.java @@ -1,4 +1,4 @@ -@Version("7.3.0") +@Version("7.5.0") package aQute.bnd.osgi; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/repository/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/repository/package-info.java index 6970bc3c87..7eda628c74 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/repository/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/repository/package-info.java @@ -1,6 +1,6 @@ /** */ -@Version("3.2.0") +@Version("3.3.0") package aQute.bnd.osgi.repository; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/print/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/print/package-info.java index f3d2ff8f22..3a5efa2640 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/print/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/print/package-info.java @@ -1,4 +1,4 @@ -@Version("2.2.0") +@Version("2.3.0") package aQute.bnd.print; import org.osgi.annotation.versioning.Version; diff --git a/biz.aQute.bndlib/src/aQute/bnd/service/generate/package-info.java b/biz.aQute.bndlib/src/aQute/bnd/service/generate/package-info.java index 8c6d59e10d..9b6b45c7e8 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/service/generate/package-info.java +++ b/biz.aQute.bndlib/src/aQute/bnd/service/generate/package-info.java @@ -1,2 +1,2 @@ -@org.osgi.annotation.versioning.Version("2.2.0") +@org.osgi.annotation.versioning.Version("2.3.0") package aQute.bnd.service.generate; From 4520408077befc5b65bb6e45bd9f22f59d6c2a7a Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Jan 2025 16:11:19 +0100 Subject: [PATCH 13/14] Support converters/formatters for merge props We now maintain a list of merge property keys in Constants. The BndEdit Model now try to see if a property is part of a merge header and use the proper converter/formatter. --- Signed-off-by: github-actions Signed-off-by: github-actions --- .../aQute/bnd/build/model/BndEditModel.java | 26 +++++++- .../src/aQute/bnd/osgi/Constants.java | 65 ++++++++++++++++--- 2 files changed, 79 insertions(+), 12 deletions(-) diff --git a/biz.aQute.bndlib/src/aQute/bnd/build/model/BndEditModel.java b/biz.aQute.bndlib/src/aQute/bnd/build/model/BndEditModel.java index 26459b3d0c..562cec046c 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/build/model/BndEditModel.java +++ b/biz.aQute.bndlib/src/aQute/bnd/build/model/BndEditModel.java @@ -1580,16 +1580,35 @@ public boolean isCnf() { @SuppressWarnings("unchecked") public static String format(String header, String input) { - Converter converter = (Converter) converters.get(header); + Converter converter = getConverter(converters, header); if (converter == null) return input; T converted = converter.convert(input); - Converter formatter = (Converter) formatters.get(header); + Converter formatter = getConverter(formatters, header); return formatter.convert(converted); } + @SuppressWarnings({ + "unchecked", "rawtypes" + }) + private static Converter getConverter(Map converters, String header) { + String stem = getStem(header); + if (Constants.MERGED_HEADERS.contains(stem)) { + return (Converter) converters.get(stem); + } else + return (Converter) converters.get(header); + } + + private static String getStem(String header) { + int n = header.indexOf('.'); + if (n < 0) + return header; + + return header.substring(0, n); + } + @SuppressWarnings("unchecked") public > String add(String header, String toAdd) { try { @@ -1656,7 +1675,8 @@ public Properties getDocumentProperties() { } /** - * Return if this model is handling effective properties (and this read only) or actual document properties. + * Return if this model is handling effective properties (and this read + * only) or actual document properties. */ public boolean isEffective() { diff --git a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java index 2d45a7ca60..346d838dae 100644 --- a/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java +++ b/biz.aQute.bndlib/src/aQute/bnd/osgi/Constants.java @@ -523,15 +523,13 @@ public interface Constants { * Headers that if **all** are absent will trigger the -includepackage * `*;from:=project` (include all packages from the project's output. */ - Set EXPAND_HEADERS = Sets.of( - Constants.RESOURCEONLY, Constants.INCLUDEPACKAGE, Constants.PRIVATE_PACKAGE, Constants.PRIVATEPACKAGE, - Constants.EXPORT_PACKAGE - ); + Set EXPAND_HEADERS = Sets.of(Constants.RESOURCEONLY, Constants.INCLUDEPACKAGE, + Constants.PRIVATE_PACKAGE, Constants.PRIVATEPACKAGE, Constants.EXPORT_PACKAGE); /** * Marker resource set by the ProjectBuilder to mark a JAR as the project - * output's entry in the classpath. Used for the - * {@link #EXPAND_HEADERS} processing. + * output's entry in the classpath. Used for the {@link #EXPAND_HEADERS} + * processing. */ String PROJECT_MARKER = "META-INF/.project"; @@ -580,7 +578,6 @@ public interface Constants { String SERVICELOADER_REGISTER_DIRECTIVE = "register:"; String SERVICELOADER_NAMESPACE = "osgi.serviceloader"; - /** * Launch constants that should be shared by launchers */ @@ -600,6 +597,58 @@ public interface Constants { String LAUNCH_ACTIVATORS = "launch.activators"; String LAUNCH_ACTIVATION_EAGER = "launch.activation.eager"; + /** + * A list of headers that use merged properties + */ + Set MERGED_HEADERS = Set.of( // + AUGMENT, // + BUILDERIGNORE, // + BUILDREPO, // + BUILDPATH, // + CONDITIONAL_PACKAGE, // + CONNECTION_SETTINGS, // + DEFINE_CONTRACT, // + DEFAULT_PROP_SRC_DIR, // + DEPENDSON, // + DONOTCOPY, // + DSANNOTATIONS_OPTIONS, // + EXPORT_PACKAGE, // + EXTENSION, // + FIXUPMESSAGES, // + GESTALT, // + IMPORT_PACKAGE, // + INCLUDERESOURCE, // + MAKE, // + MAVEN_DEPENDENCIES, // + PLUGIN, // + PLUGINPATH, // + PREPROCESSMATCHERS, // + PRIVATE_PACKAGE, // + PROVIDE_CAPABILITY, // + RELEASEREPO, // + REMOVEHEADERS, // + REQUIRE_CAPABILITY, // + RESOLVE_REJECT, // + RUNBLACKLIST, // + RUNBUNDLES, // + RUNBUNDLES_DECORATOR, // + RUNPATH, // + RUNPROGRAMARGS, // + RUNPROPERTIES, // + RUNPROVIDEDCAPABILITIES, // + RUNREPOS, // + RUNREQUIRES, // + RUNSYSTEMCAPABILITIES, // + RUNSYSTEMPACKAGES, // + RUNVM, // + SOURCEPATH, // + STANDALONE, // + SYSTEMPROPERTIES, // + TESTPACKAGES, // + TESTPATH, // + WORKINGSET, // + "-pomaugment"); + /** * Any attributes that should be removed from the attributes before * printing. @@ -615,8 +664,6 @@ public interface Constants { String INTERNAL_PREFIX = "-internal-"; - - /* * Deprecated Section */ From d5bb73883875cd3a0a389304ab70aaafa769550c Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Jan 2025 16:11:39 +0100 Subject: [PATCH 14/14] Diverse changes in the UI --- Signed-off-by: github-actions Signed-off-by: github-actions --- .../src/bndtools/editor/BndEditor.java | 2 +- .../editor/BndSourceEffectivePage.java | 292 +++++++++++------- 2 files changed, 173 insertions(+), 121 deletions(-) diff --git a/bndtools.core/src/bndtools/editor/BndEditor.java b/bndtools.core/src/bndtools/editor/BndEditor.java index b03c872070..e30e29a5cf 100644 --- a/bndtools.core/src/bndtools/editor/BndEditor.java +++ b/bndtools.core/src/bndtools/editor/BndEditor.java @@ -151,8 +151,8 @@ private void updateIncludedPages() { } else { requiredPageIds.addAll(getPagesBnd(path)); } - requiredPageIds.add(SOURCE_PAGE); requiredPageIds.add(SOURCE_PAGE_EFFECTIVE); + requiredPageIds.add(SOURCE_PAGE); // Remove pages no longer required and remember the rest in a map int i = 0; diff --git a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java index 0fb75716ed..f93245b521 100644 --- a/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java +++ b/bndtools.core/src/bndtools/editor/BndSourceEffectivePage.java @@ -1,9 +1,12 @@ package bndtools.editor; import java.io.File; +import java.net.URI; import java.util.List; +import java.util.Map; import java.util.ResourceBundle; import java.util.Set; +import java.util.function.Function; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; @@ -22,24 +25,30 @@ import org.eclipse.jface.text.source.LineNumberRulerColumn; import org.eclipse.jface.text.source.SourceViewer; import org.eclipse.jface.viewers.ArrayContentProvider; -import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ColumnViewerToolTipSupport; import org.eclipse.jface.viewers.DoubleClickEvent; import org.eclipse.jface.viewers.IDoubleClickListener; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.TableViewer; import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.ViewerCell; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StackLayout; import org.eclipse.swt.custom.StyledText; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.events.ControlListener; import org.eclipse.swt.events.SelectionAdapter; import org.eclipse.swt.events.SelectionEvent; import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; import org.eclipse.swt.layout.GridData; import org.eclipse.swt.layout.GridLayout; import org.eclipse.swt.widgets.Button; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; import org.eclipse.ui.IActionBars; import org.eclipse.ui.IEditorInput; import org.eclipse.ui.IWorkbenchPart; @@ -56,10 +65,12 @@ import org.eclipse.ui.part.FileEditorInput; import org.eclipse.ui.texteditor.FindReplaceAction; +import aQute.bnd.build.Workspace; import aQute.bnd.build.model.BndEditModel; import aQute.bnd.exceptions.Exceptions; import aQute.bnd.osgi.Processor; import aQute.bnd.osgi.Processor.PropertyKey; +import bndtools.central.Central; import bndtools.editor.completion.BndSourceViewerConfiguration; /** @@ -70,16 +81,15 @@ */ public class BndSourceEffectivePage extends FormPage { - - private final BndEditor bndEditor; + private final BndEditor bndEditor; private BndEditModel editModel; private SourceViewer sourceViewer; private TableViewer tableViewer; - private StyledText styledText; + private StyledText styledText; private Button toggleButton; private Composite viewersComposite; private StackLayout stackLayout; - private boolean loading; + private boolean loading; public BndSourceEffectivePage(FormEditor formEditor, String id, String title) { super(formEditor, id, title); @@ -94,7 +104,6 @@ protected void createFormContent(IManagedForm managedForm) { scrolledForm.setExpandHorizontal(true); scrolledForm.setExpandVertical(true); - Form form = scrolledForm.getForm(); toolkit.setBorderStyle(SWT.NULL); @@ -147,7 +156,6 @@ public void widgetSelected(SelectionEvent e) { private void createSourceViewer(IManagedForm managedForm, Composite body) { - // ruler for line numbers CompositeRuler ruler = new CompositeRuler(); LineNumberRulerColumn ln = new LineNumberRulerColumn(); @@ -187,12 +195,13 @@ private void createTableViewer(IManagedForm managedForm, Composite body) { this.tableViewer = new TableViewer(body, SWT.BORDER | SWT.FULL_SELECTION | SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + Table table = tableViewer.getTable(); table.setHeaderVisible(true); table.setLinesVisible(true); - table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - createColumns(); + int[] widths = createColumns(tableViewer); + tableViewer.setContentProvider(ArrayContentProvider.getInstance()); tableViewer.setInput(getTableData()); @@ -200,42 +209,86 @@ private void createTableViewer(IManagedForm managedForm, Composite body) { @Override public void doubleClick(DoubleClickEvent event) { IStructuredSelection selection = tableViewer.getStructuredSelection(); - Object firstElement = selection.getFirstElement(); - - if(firstElement instanceof PropertyKey prop) { - String fpath = getPropertyKeyPath(prop); - if (fpath == null || fpath.isBlank()) { - return; - } + for (Object element : selection.toList()) { + open(element); + } + } + }); - IWorkspaceRoot root = ResourcesPlugin.getWorkspace() - .getRoot(); + tableViewer.getControl() + .addControlListener(new ControlListener() { - File file = new File(fpath); - org.eclipse.core.runtime.IPath path = new Path(file.getAbsolutePath()); - IFile iFile = root.getFileForLocation(path); - if (iFile == null) { - // File is not in the workspace. You cannot directly get - // an IFile for it. - } - IEditorInput input = new FileEditorInput(iFile); - try { - PlatformUI.getWorkbench() - .getActiveWorkbenchWindow() - .getActivePage() - .openEditor(input, BndEditor.WORKSPACE_EDITOR); - } catch (PartInitException e) { - throw Exceptions.duck(e); + @Override + public void controlResized(ControlEvent arg0) { + Rectangle rect = tableViewer.getTable() + .getClientArea(); + if (rect.width > 0) { + int total = rect.width; + int selected = -1; + + for (int i = 0; i < widths.length; i++) { + TableColumn column = tableViewer.getTable() + .getColumn(i); + int w = widths[i]; + if (w > 0) { + total -= column.getWidth(); + continue; + } + selected = i; + } + if (selected >= 0) { + TableColumn column = tableViewer.getTable() + .getColumn(selected); + int minwidth = -widths[selected]; + if (minwidth < total) { + column.setWidth(total); + } else { + column.setWidth(minwidth); + } + } } } - } - }); + @Override + public void controlMoved(ControlEvent arg0) { + } + }); tableViewer.getControl() .getParent() .layout(true, true); } + private void open(Object element) { + if (element instanceof PropertyKey prop) { + String fpath = prop.getProvenance() + .orElse(null); + if (fpath == null || fpath.isBlank()) { + return; + } + IWorkspaceRoot root = ResourcesPlugin.getWorkspace() + .getRoot(); + + File file = new File(fpath); + if (!file.isFile()) { + return; + } + + org.eclipse.core.runtime.IPath path = new Path(file.getAbsolutePath()); + IFile iFile = root.getFileForLocation(path); + if (iFile == null || !iFile.exists()) { + return; + } + IEditorInput input = new FileEditorInput(iFile); + try { + PlatformUI.getWorkbench() + .getActiveWorkbenchWindow() + .getActivePage() + .openEditor(input, BndEditor.WORKSPACE_EDITOR); + } catch (PartInitException e) { + throw Exceptions.duck(e); + } + } + } /** * Setup Eclipse's built in Search / Replace dialog. Also see @@ -245,8 +298,7 @@ public void doubleClick(DoubleClickEvent event) { * @param site * @param page */ - private static void activateFindAndReplace(TextViewer textViewer, IWorkbenchPartSite site, - IWorkbenchPart page) { + private static void activateFindAndReplace(TextViewer textViewer, IWorkbenchPartSite site, IWorkbenchPart page) { FindReplaceAction findReplaceAction = new FindReplaceAction( ResourceBundle.getBundle("org.eclipse.ui.texteditor.ConstructedEditorMessages"), "Editor.FindReplace.", page); @@ -265,7 +317,6 @@ public Object execute(ExecutionEvent event) throws ExecutionException { handlerService.activateHandler("org.eclipse.ui.edit.findReplace", handler); } - @Override public boolean isEditor() { return true; @@ -279,8 +330,7 @@ public void setActive(boolean active) { if (active) { update(); - } else { - } + } else {} } private static String print(BndEditModel model) throws Exception { @@ -309,7 +359,6 @@ private static String print(BndEditModel model) throws Exception { } - private void update() { if (loading || styledText == null || !isActive()) { return; @@ -342,106 +391,95 @@ public T getAdapter(Class adapter) { return super.getAdapter(adapter); } - // Table - private void createColumns() { - String[] titles = { - "key", "value", "path" - }; - int[] bounds = { - 200, 300, 300 - }; + private int[] createColumns(TableViewer tableViewer) { + record ColSpec(String title, int width, Function label, + Function tooltip) {} - for (int i = 0; i < titles.length; i++) { + ColSpec[] specs = { + new ColSpec("Key", 150, PropertyKey::key, null), // + new ColSpec("Value", -250, PropertyKey::getRawValue, this::getExpandedValue), // + new ColSpec("Provenance", 150, this::toPath, null), // + }; + int[] widths = new int[specs.length]; + ColumnViewerToolTipSupport.enableFor(tableViewer); + tableViewer.addDoubleClickListener(event -> { + System.out.println(event); + }); + int colIndex = 0; + for (ColSpec spec : specs) { final TableViewerColumn column = new TableViewerColumn(tableViewer, SWT.NONE); - column.getColumn() - .setText(titles[i]); - column.getColumn() - .setWidth(bounds[i]); - column.getColumn() - .setResizable(true); - column.getColumn() - .setMoveable(true); - - final int colNum = i; - column.setLabelProvider(new ColumnLabelProvider() { + TableColumn tc = column.getColumn(); + tc.setText(spec.title); + tc.setResizable(true); + tc.setMoveable(true); + + column.setLabelProvider(new CellLabelProvider() { @Override - public String getText(Object element) { - return String.valueOf(getColumnContent(element, titles[colNum])); + public void update(ViewerCell cell) { + Object element = cell.getElement(); + if (element instanceof PropertyKey pkey) { + String text = spec.label != null ? spec.label.apply(pkey) : null; + cell.setText(text); + } } - }); - final int columnIndex = i; - column.getColumn() - .addListener(SWT.Selection, e -> { - // Comparator comparator = Comparator.comparing(obj - // -> getColumnValue(obj, columnIndex)); - // tableViewer.setComparator(new - // ViewerComparator(comparator)); - }); + @Override + public String getToolTipText(Object element) { + if (spec.tooltip != null && element instanceof PropertyKey pkey) { + String s = spec.tooltip.apply(pkey); + if (s != null && s.length() > 30) { + return BndEditModel.format(pkey.key(), s); + } else + return s; + } + return null; + } + }); + tc.setWidth(spec.width < 0 ? -spec.width : spec.width); + widths[colIndex] = spec.width; + colIndex++; } + return widths; } - private Object getColumnContent(Object element, String colName) { - if (element instanceof PropertyKey prop) { - switch (colName) { - case "key" : return prop.key(); - case "value" : - return prop.processor() - .getProperty(prop.key()); - case "path" : { - String path = getPropertyKeyPath(prop); - if (path.isBlank()) { - return path; - } + private String getExpandedValue(PropertyKey k) { + Processor p = getProperties(); + return p.getProperty(k.key()); + } - // cut the beginning to get only e.g. /cnf/build.bnd instead - // of absolute path - return path.replaceAll(prop.processor() - .getBase() - .getPath(), "") - .substring(1); - } + private String toPath(Object element) { + if (element instanceof PropertyKey prop) { - default : - throw new IllegalArgumentException("Unknown column: " + colName); + String path = prop.getProvenance() + .orElse(null); + if (path == null || path.isBlank()) { + return ""; } - } - return null; - } + File file = new File(path); + if (!file.isFile()) + return path; - private String getPropertyKeyPath(PropertyKey prop) { - String path = ""; + Workspace workspace = Central.getWorkspaceIfPresent(); + if (workspace == null) + return path; - File propertiesFile = prop.processor() - .getPropertiesFile(); - if (propertiesFile != null) { - path = propertiesFile.getPath(); + URI wsbase = workspace.getBase() + .toURI(); + URI fbase = file.toURI(); + URI relative = wsbase.relativize(fbase); + return relative.getPath(); } - return path; + return ""; } - private Object[] getTableData() { + Object[] getTableData() { try { - - Processor p = new BndEditModel(editModel, true).getProperties(); + Processor p = getProperties(); List propertyKeys = p.getPropertyKeys(k -> true); - - // avoid duplicates because Project is parent of bnd.bnd and also - // gets the same properties - // but with higher floor List visible = PropertyKey.findVisible(propertyKeys); - - Object[] result = new Object[visible.size()]; - int index = 0; - - for (PropertyKey prop : visible) { - result[index] = prop; - index++; - } - - return result; + return visible.toArray(); } catch (Exception e) { throw Exceptions.duck(e); @@ -449,4 +487,18 @@ private Object[] getTableData() { } + Processor getProperties() { + Map changes = editModel.getDocumentChanges(); + Processor p = new Processor(editModel.getOwner()) { + @Override + public String getUnexpandedProperty(String key) { + if (changes.containsKey(key)) { + return changes.get(key); + } + return super.getUnexpandedProperty(key); + } + }; + return p; + } + }