Home » 2023 » Januar

Archiv für den Monat: Januar 2023

NAT Tree List with GlazedLists and NatTable

This is my exercise code to create a Tree List with expand/collapsible subgroups.

The code was derived from Vogellas Nebula NatTable – Commands & Events – Tutorial.

My Code

The data rows of the table:

package tryout.treelist;

public class Area {
	private String name;
	private long population;
	
	public Area(String name, long population) {
		super();
		this.name = name;
		this.population = population;
	}
	
	public String getName() {
		return name;
	}
	public long getPopulation() {
		return population;
	}
	
	@Override
	public String toString() {
		return name + " Population: " + population;
	}
}

The tree node wrapper

package tryout.treelist;

import java.util.ArrayList;
import java.util.List;

public class TreeItem<T> {
	private T item;
	private TreeItem<T> parent;
	private List<TreeItem<T>> children = new ArrayList<>();
	
	public TreeItem(T item, TreeItem<T> parent){
		this.item=item;
		this.parent=parent;
		this.parent.addChild(this);
	}
	public TreeItem(T content) {
		this.item=content;
	}
	
	private void addChild(TreeItem<T> treeItem) {
		children.add(treeItem);		
	}

	public TreeItem<T> getParent() {
		return parent;
	}

	public boolean hasChildren() {
		return children.size() > 0;
	}

	public T getItem() {
		return item;
	}

}

The tree format definition for such nodes:

package tryout.treelist;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

import ca.odell.glazedlists.TreeList;

class TreeItemFormat implements TreeList.Format<TreeItem<Area>> {

    @Override
    public void getPath(List<TreeItem<Area>> path, TreeItem<Area> element) {
    	List<TreeItem<Area>> path1 = new ArrayList<>();
        if (element.getParent() != null) {
        	getPath(path1, element.getParent());
            path.addAll(path1);
        }
        path.add(element);
    }

    @Override
    public boolean allowsChildren(TreeItem<Area> element) {
        return element.hasChildren();
    }

    @Override
    public Comparator<? super TreeItem<Area>> getComparator(int depth) {
        return (o1, o2) -> o1.getItem().toString().compareTo(o2.getItem().toString());
    }
}

The GUI view defining the NAT table and the data fed to it:

package tryout.treelist;

import java.util.Collection;

import javax.annotation.PostConstruct;

import org.eclipse.jface.layout.GridLayoutFactory;
import org.eclipse.nebula.widgets.nattable.NatTable;
import org.eclipse.nebula.widgets.nattable.data.ExtendedReflectiveColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.IColumnPropertyAccessor;
import org.eclipse.nebula.widgets.nattable.data.ListDataProvider;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeData;
import org.eclipse.nebula.widgets.nattable.extension.glazedlists.tree.GlazedListTreeRowModel;
import org.eclipse.nebula.widgets.nattable.grid.GridRegion;
import org.eclipse.nebula.widgets.nattable.layer.DataLayer;
import org.eclipse.nebula.widgets.nattable.tree.TreeLayer;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;

import com.google.common.collect.ImmutableList;

import ca.odell.glazedlists.EventList;
import ca.odell.glazedlists.GlazedLists;
import ca.odell.glazedlists.TreeList;

public class SimpleTreePart {

    @PostConstruct
    public void createComposite(Composite parent) {
        parent.setLayout(new GridLayout());
        // Properties of the Area items inside the TreeItems
        String[] propertyNames = { "item.name", "item.population" };

        IColumnPropertyAccessor<TreeItem<Area>> columnPropertyAccessor = new ExtendedReflectiveColumnPropertyAccessor<TreeItem<Area>>(
                propertyNames);

        EventList<TreeItem<Area>> eventList = GlazedLists.eventList(getSampleAreaTreeItems());
        TreeList<TreeItem<Area>> treeList = new TreeList<TreeItem<Area>>(eventList, new TreeItemFormat(),
                TreeList.nodesStartExpanded());
        ListDataProvider<TreeItem<Area>> dataProvider = new ListDataProvider<>(treeList, columnPropertyAccessor);
        DataLayer dataLayer = new DataLayer(dataProvider);
        setColumWidthPercentage(dataLayer);

        GlazedListTreeData<TreeItem<Area>> glazedListTreeData = new GlazedListTreeData<>(treeList);
        GlazedListTreeRowModel<TreeItem<Area>> glazedListTreeRowModel = new GlazedListTreeRowModel<>(
                glazedListTreeData);

        TreeLayer treeLayer = new TreeLayer(dataLayer, glazedListTreeRowModel);
        treeLayer.setRegionName(GridRegion.BODY);

        new NatTable(parent, treeLayer, true);
        

        GridLayoutFactory.fillDefaults().generateLayout(parent);
    }

    private Collection<TreeItem<Area>> getSampleAreaTreeItems() {
		TreeItem<Area> globe = new TreeItem<Area>(new Area("Globe", 8000));
		
		TreeItem<Area> europe = new TreeItem<Area>(new Area("Europe", 983), globe);
		TreeItem<Area> switzerland = new TreeItem<Area>(new Area("Switzerland", 8), europe);
		TreeItem<Area> germany = new TreeItem<Area>(new Area("Germany", 85), europe);
		TreeItem<Area> russia = new TreeItem<Area>(new Area("Russia", 140), europe);
		
		TreeItem<Area> asia = new TreeItem<Area>(new Area("Asia", 3203), globe);
		TreeItem<Area> japan = new TreeItem<Area>(new Area("Japan", 203), asia);
		TreeItem<Area> china = new TreeItem<Area>(new Area("China", 1230), asia);
		TreeItem<Area> india = new TreeItem<Area>(new Area("India", 1130), asia);
		
	
		return ImmutableList.of(globe, asia, japan, china, india, europe, switzerland, germany, russia);
	}

	private void setColumWidthPercentage(DataLayer dataLayer) {
        dataLayer.setColumnPercentageSizing(true);
        dataLayer.setColumnWidthPercentageByPosition(0, 50);
        dataLayer.setColumnWidthPercentageByPosition(1, 50);
    }
}

And the main class to run the code:

package tryout.treelist;

import org.eclipse.swt.layout.RowLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;


public class TreeListMain {

	public static void main(String[] args) {
		Display display = new Display();
		Shell shell = new Shell(display);
		shell.setSize(300, 130);
		shell.setText("Tree List Example");
		shell.setLayout(new RowLayout());

		new SimpleTreePart().createComposite(shell);
		shell.open();
		while (!shell.isDisposed()) {
			if (!display.readAndDispatch())
				display.sleep();
		}
		display.dispose();
	}
}

Jenkins Trigger Child Job

Notizen zu meiner Odysse zum Versuch, aus einem Jenkins Job einen Folge-Job mit jeweils unterschiedlichen Parametern aufzurufen.

Wie es funktioniert

Variante 1 (getestet)

Das Parameterized Trigger Plugin, wenn es in der Build (nicht Post-Build) Phase configuriert wird so aufgerufen werden, dass für jedes Configurations-File (welches auf das definierte File-Name-Pattern passt) einmal der definierte Job aufgerufen wird. (–> Hier der Hinweis)

Wenn das Parameterized Trigger Plugin jedoch in der Post-Build phase configuriert wird ist das Trigger-Pro-File Feature leider nicht konfigurierbar.

Variante 2 (nicht getestet)

Aufruf über HTTP. Diese Variante hab ich nicht ausgetestet, weil ich befürchtet habe in Jenkins meines Arbeitgebers nicht so leicht an die Authorisierungs-Tokens zu gelangen und eine so geartete Lösung produktiv so liefern zu können.

Links:

Was ich auch noch gelernt habe

Aufruf von Folge-Jobs via Groovy

Leider habe ich es nicht geschaft dem Groovy-mässigen Aufruf auch Parameter mit zu geben.

Der folgende Code, eingefügt in in Groovy Plugin in Post-Build ruft tatsächlich den Folge-Job auf (aber leider one Parameter):

import jenkins.*
import jenkins.model.*
import hudson.*
import hudson.model.*

def build = Thread.currentThread().executable
def buildNumber = build.number
def target = Jenkins.instance.getItemByFullName("Mein_Jenkins_Job") ?: null
if (!target) {
	manager.listener.logger.println("No downstream job found")
} else {
	manager.listener.logger.println("I found it!")
  	def execution = target.scheduleBuild2(0, new Cause.UpstreamCause(build))
  	manager.listener.logger.println("Running test job:")
  	manager.listener.logger.println("Complete, result was: " + execution.get().result)
}

Dokumentation hudson.model.AbstractProject Klasse, dessen scheduleBuild2() Methode aufgerufen wird.
Die doBuildWithParameters Methode ist leider deprecated und für den Ersatz sind keine Beispiele auffindbar.
–> Inspiration für diesen Code
–> Groovy Syntax

Definition Folge-Job als Multi-Configuration-Projekt & Aufruf einer spezifischen Ausprägung davon

Multikonfigurations-Jobs haben typischerweise eine Dimension in der sie sich unterscheiden, z.B. die Dimension Betriebssystem (Windows, Linux, GoogleOS, …). Für jede dieser Dimensionen wird eine Instanz generiert. Wenn man den Job startet, wird per Default für jede der Ausprägungen eine Instanz gestartet.
Nun sah es vielsversprechend aus, dass man einen solchen Job unter Mitgabe einer Dimensions-Ausprägung hätte staten können. Ich war bislang nicht er folgreich.

Readings: