diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml
index 68cae27e..d679076c 100644
--- a/.github/workflows/maven.yml
+++ b/.github/workflows/maven.yml
@@ -28,6 +28,8 @@ jobs:
- name: Build and Deploy with Maven
env:
COVERALLS_TOKEN: ${{ secrets.COVERALLS_TOKEN }}
- run: ./mvnw -B deploy coveralls:report
+ uses: GabrielBB/xvfb-action@v1
+ with:
+ run: ./mvnw -B deploy coveralls:report
- name: Clear Caches
run: curl -X PURGE https://camo.githubusercontent.com/8b5dee301fc3aee86900e1db08654773df3bd0938cd1d1009be98a34deb7478b/68747470733a2f2f636f766572616c6c732e696f2f7265706f732f6769746875622f6c696e6b2d696e74657273797374656d732f6c69732d636f6d6d6f6e732f62616467652e7376673f6272616e63683d6d6173746572
diff --git a/lis-commons-beans-mockito/README.md b/lis-commons-beans-mockito/README.md
new file mode 100644
index 00000000..401bb7f1
--- /dev/null
+++ b/lis-commons-beans-mockito/README.md
@@ -0,0 +1,23 @@
+# lis-commons-beans-mockito
+
+A mockito extension for bean related stuff.
+
+## BeanMatchers
+
+The BeanMatchers class provides support for java bean related equality matching.
+
+> verify(personSetter, times(1)).setPerson(BeanMatchers.propertiesEqual(expectedPerson));
+>
+> // or exclude properties
+>
+> verify(personSetter, times(1)).setPerson(BeanMatchers.propertiesEqual(expectedPerson,"firstname"));
+
+### Custom BeanMatcher
+
+You can also create a custom BeanMatcher with another BeansFactory. E.g. one can create BeanMatcher that supports
+Java records.
+
+> BeansFactory recordBeansFactory = BeansFactory.getInstance("record");
+> BeanMatcher recordBeanMatcher = new BeanMatcher(recordBeansFactory);
+>
+> verify(personSetter, times(1)).setPerson(recordBeanMatcher.propertiesEqual(expectedPersonRecord));
\ No newline at end of file
diff --git a/lis-commons-beans-mockito/pom.xml b/lis-commons-beans-mockito/pom.xml
new file mode 100644
index 00000000..46be8062
--- /dev/null
+++ b/lis-commons-beans-mockito/pom.xml
@@ -0,0 +1,27 @@
+
+
+ 4.0.0
+
+ com.link-intersystems.commons
+ lis-commons
+ 1.9.7-SNAPSHOT
+
+
+ lis-commons-beans-mockito
+ A mockito extension for bean related stuff.
+
+
+
+ com.link-intersystems.commons
+ lis-commons-beans
+
+
+ org.mockito
+ mockito-core
+ compile
+
+
+
+
\ No newline at end of file
diff --git a/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatcher.java b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatcher.java
new file mode 100644
index 00000000..83cfd135
--- /dev/null
+++ b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatcher.java
@@ -0,0 +1,31 @@
+package com.link_intersystems.beans.mockito;
+
+import com.link_intersystems.beans.BeansFactory;
+import org.mockito.ArgumentMatcher;
+
+import static java.util.Objects.requireNonNull;
+import static org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress;
+
+public class BeanMatcher {
+
+ public static final BeanMatcher DEFAULT = new BeanMatcher();
+
+ private BeansFactory beansFactory;
+
+ public BeanMatcher() {
+ this(BeansFactory.getDefault());
+ }
+
+ public BeanMatcher(BeansFactory beansFactory) {
+ this.beansFactory = requireNonNull(beansFactory);
+ }
+
+ public T propertiesEqual(Object aBean, String... excludeProperties) {
+ reportMatcher(new PropertiesMatcher<>(aBean, beansFactory, excludeProperties));
+ return null;
+ }
+
+ private void reportMatcher(ArgumentMatcher> matcher) {
+ mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher);
+ }
+}
diff --git a/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatchers.java b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatchers.java
new file mode 100644
index 00000000..21e2b317
--- /dev/null
+++ b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/BeanMatchers.java
@@ -0,0 +1,17 @@
+package com.link_intersystems.beans.mockito;
+
+import com.link_intersystems.beans.BeansFactory;
+
+public class BeanMatchers {
+
+ public static T propertiesEqual(Object aBean, String... excludeProperties) {
+ return BeanMatcher.DEFAULT.propertiesEqual(aBean, excludeProperties);
+ }
+
+ public static T propertiesEqual(BeansFactory beansFactory, Object aBean, String... excludeProperties) {
+ BeanMatcher beanMatcher = new BeanMatcher(beansFactory);
+ beanMatcher.propertiesEqual(aBean, excludeProperties);
+ return null;
+ }
+
+}
diff --git a/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/PropertiesMatcher.java b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/PropertiesMatcher.java
new file mode 100644
index 00000000..f9afdc92
--- /dev/null
+++ b/lis-commons-beans-mockito/src/main/java/com/link_intersystems/beans/mockito/PropertiesMatcher.java
@@ -0,0 +1,44 @@
+package com.link_intersystems.beans.mockito;
+
+import com.link_intersystems.beans.Bean;
+import com.link_intersystems.beans.BeansFactory;
+import com.link_intersystems.beans.Property;
+import com.link_intersystems.beans.PropertyList;
+import org.mockito.ArgumentMatcher;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.Predicate;
+
+import static java.util.Objects.requireNonNull;
+
+public class PropertiesMatcher implements ArgumentMatcher {
+
+ private final Bean expectedBean;
+ private final List excludeProperties;
+ private final BeansFactory beansFactory;
+
+ public PropertiesMatcher(T expectedBean, String... excludeProperties) {
+ this(expectedBean, BeansFactory.getDefault(), excludeProperties);
+ }
+
+ public PropertiesMatcher(T expectedBean, BeansFactory beansFactory, String... excludeProperties) {
+ this.beansFactory = requireNonNull(beansFactory);
+ this.expectedBean = beansFactory.createBean(expectedBean, Object.class);
+ this.excludeProperties = Arrays.asList(excludeProperties);
+ }
+
+ @Override
+ public boolean matches(T argument) {
+ Bean actualBean = beansFactory.createBean(argument, Object.class);
+
+ Predicate exclucedPropertiesPredicate = pd -> !excludeProperties.contains(pd.getPropertyDesc().getName());
+ PropertyList expectedProperties = expectedBean.getProperties();
+ PropertyList expectedFilteredProperties = expectedProperties.filter(exclucedPropertiesPredicate);
+
+ PropertyList actualProperties = actualBean.getProperties();
+ PropertyList actualFilteredProperties = actualProperties.filter(exclucedPropertiesPredicate);
+
+ return expectedFilteredProperties.equals(actualFilteredProperties);
+ }
+}
diff --git a/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/BeanMatchersTest.java b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/BeanMatchersTest.java
new file mode 100644
index 00000000..29972795
--- /dev/null
+++ b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/BeanMatchersTest.java
@@ -0,0 +1,48 @@
+package com.link_intersystems.beans.mockito;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static com.link_intersystems.beans.mockito.BeanMatchers.propertiesEqual;
+import static org.mockito.Mockito.*;
+
+class BeanMatchersTest {
+
+ interface PersonSetter {
+ public void setPerson(Person person);
+ }
+
+ private Person johnDoe;
+ private Person janeDoe;
+
+ @BeforeEach
+ void setUp() {
+ PersonFixture personFixture = new PersonFixture();
+ johnDoe = personFixture.getJohnDoe();
+ janeDoe = personFixture.getJaneDoe();
+ }
+
+ @Test
+ void testPropertiesEqual() {
+ PersonSetter personSetter = mock(PersonSetter.class);
+
+ personSetter.setPerson(johnDoe);
+
+ Person johnDoeCopy = new Person();
+ johnDoeCopy.setFirstname("John");
+ johnDoeCopy.setLastname("Doe");
+ johnDoeCopy.setAge(37);
+
+ verify(personSetter, times(1)).setPerson(propertiesEqual(johnDoeCopy));
+ }
+
+ @Test
+ void testPropertiesEqualExcludes() {
+ PersonSetter personSetter = mock(PersonSetter.class);
+
+ personSetter.setPerson(johnDoe);
+
+
+ verify(personSetter, times(1)).setPerson(propertiesEqual(janeDoe, "firstname", "age"));
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/Person.java b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/Person.java
new file mode 100644
index 00000000..b8e9d526
--- /dev/null
+++ b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/Person.java
@@ -0,0 +1,32 @@
+package com.link_intersystems.beans.mockito;
+
+public class Person {
+
+ String firstname;
+ String lastname;
+ int age;
+
+ public String getFirstname() {
+ return firstname;
+ }
+
+ public void setFirstname(String firstname) {
+ this.firstname = firstname;
+ }
+
+ public String getLastname() {
+ return lastname;
+ }
+
+ public void setLastname(String lastname) {
+ this.lastname = lastname;
+ }
+
+ public int getAge() {
+ return age;
+ }
+
+ public void setAge(int age) {
+ this.age = age;
+ }
+}
diff --git a/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PersonFixture.java b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PersonFixture.java
new file mode 100644
index 00000000..ee92cd55
--- /dev/null
+++ b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PersonFixture.java
@@ -0,0 +1,24 @@
+package com.link_intersystems.beans.mockito;
+
+public class PersonFixture {
+
+ public Person getJohnDoe() {
+ Person johnDoe = new Person();
+
+ johnDoe.setFirstname("John");
+ johnDoe.setLastname("Doe");
+ johnDoe.setAge(37);
+
+ return johnDoe;
+ }
+
+ public Person getJaneDoe() {
+ Person janeDoe = new Person();
+
+ janeDoe.setFirstname("Jane");
+ janeDoe.setLastname("Doe");
+ janeDoe.setAge(35);
+
+ return janeDoe;
+ }
+}
diff --git a/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PropertiesMatcherTest.java b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PropertiesMatcherTest.java
new file mode 100644
index 00000000..1448b387
--- /dev/null
+++ b/lis-commons-beans-mockito/src/test/java/com/link_intersystems/beans/mockito/PropertiesMatcherTest.java
@@ -0,0 +1,36 @@
+package com.link_intersystems.beans.mockito;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+class PropertiesMatcherTest {
+
+ private PersonFixture personFixture;
+ private PropertiesMatcher johnDoeMatcher;
+
+ @BeforeEach
+ void setUp() {
+ personFixture = new PersonFixture();
+ johnDoeMatcher = new PropertiesMatcher<>(personFixture.getJohnDoe());
+ }
+
+ @Test
+ void matches() {
+ assertTrue(johnDoeMatcher.matches(personFixture.getJohnDoe()));
+ }
+
+ @Test
+ void wontMatch() {
+ assertFalse(johnDoeMatcher.matches(personFixture.getJaneDoe()));
+ }
+
+ @Test
+ void matchExcludes() {
+ johnDoeMatcher = new PropertiesMatcher<>(personFixture.getJohnDoe(), "firstname", "age");
+
+ assertTrue(johnDoeMatcher.matches(personFixture.getJaneDoe()));
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-beans/pom.xml b/lis-commons-beans/pom.xml
index 91ddb5e0..c2e8e5fd 100644
--- a/lis-commons-beans/pom.xml
+++ b/lis-commons-beans/pom.xml
@@ -33,19 +33,4 @@
-
-
-
- org.apache.maven.plugins
- maven-jar-plugin
-
-
-
- test-jar
-
-
-
-
-
-
\ No newline at end of file
diff --git a/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/BeanMatchers.java b/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/BeanMatchers.java
deleted file mode 100644
index 86e7392e..00000000
--- a/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/BeanMatchers.java
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.link_intersystems.mockito.beans;
-
-import org.mockito.ArgumentMatcher;
-
-import static org.mockito.internal.progress.ThreadSafeMockingProgress.*;
-
-public class BeanMatchers {
- public static T propertiesEqual(Object aBean) {
- reportMatcher(new PropertiesMatcher<>(aBean));
- return null;
- }
-
- private static void reportMatcher(ArgumentMatcher> matcher) {
- mockingProgress().getArgumentMatcherStorage().reportMatcher(matcher);
- }
-}
diff --git a/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/PropertiesMatcher.java b/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/PropertiesMatcher.java
deleted file mode 100644
index 80e484ad..00000000
--- a/lis-commons-beans/src/test/java/com/link_intersystems/mockito/beans/PropertiesMatcher.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package com.link_intersystems.mockito.beans;
-
-import com.link_intersystems.beans.Bean;
-import com.link_intersystems.beans.BeansFactory;
-import org.mockito.ArgumentMatcher;
-
-public class PropertiesMatcher implements ArgumentMatcher {
-
- private final Bean expectedBean;
-
- public PropertiesMatcher(T expectedBean) {
- this(expectedBean, BeansFactory.getDefault());
- }
-
- public PropertiesMatcher(T expectedBean, BeansFactory beansFactory) {
- this.expectedBean = beansFactory.createBean(expectedBean, Object.class);
- }
-
- @Override
- public boolean matches(T argument) {
- BeansFactory factory = BeansFactory.getDefault();
- Bean argumentBean = factory.createBean(argument, Object.class);
- return expectedBean.propertiesEqual(argumentBean);
- }
-}
diff --git a/lis-commons-swing-view/pom.xml b/lis-commons-swing-view/pom.xml
new file mode 100644
index 00000000..faa00ecd
--- /dev/null
+++ b/lis-commons-swing-view/pom.xml
@@ -0,0 +1,46 @@
+
+
+ 4.0.0
+
+ com.link-intersystems.commons
+ lis-commons
+ 1.9.7-SNAPSHOT
+
+
+ lis-commons-swing-view
+
+
+
+
+ com.link-intersystems.commons
+ lis-commons-swing
+
+
+ com.link-intersystems.commons
+ lis-commons-util-context
+
+
+ com.link-intersystems.commons
+ lis-commons-beans-mockito
+ test
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+
+
+
+ test-jar
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractView.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractView.java
new file mode 100644
index 00000000..6f1fb618
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractView.java
@@ -0,0 +1,52 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+import com.link_intersystems.util.context.dsl.ContextDsl;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class AbstractView implements View {
+
+ private ViewSite viewSite;
+ private ContextDsl contextDsl;
+
+ @Override
+ public void install(ViewSite viewSite) {
+ this.viewSite = requireNonNull(viewSite);
+ Context viewContext = viewSite.getViewContext();
+ this.contextDsl = new ContextDsl(viewContext);
+ doInstall(viewSite);
+ }
+
+ protected ViewSite getViewSite() {
+ return viewSite;
+ }
+
+ protected ContextDsl getContextDsl() {
+ return contextDsl;
+ }
+
+ protected abstract void doInstall(ViewSite viewSite);
+
+ @Override
+ public void uninstall() {
+ if (viewSite == null) {
+ return;
+ }
+
+ doUninstall(viewSite);
+
+ viewSite = null;
+ contextDsl.dispose();
+ }
+
+ protected void doUninstall(ViewSite viewSite) {
+ ViewContent viewContent = viewSite.getViewContent();
+ viewContent.setComponent(null);
+ }
+
+ protected ViewSite createSubViewSite(ViewContent viewContent) {
+ Context viewContext = viewSite.getViewContext();
+ return new DefaultViewSite(viewContent, viewContext);
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractViewSite.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractViewSite.java
new file mode 100644
index 00000000..cc7e01a6
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/AbstractViewSite.java
@@ -0,0 +1,11 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+
+public abstract class AbstractViewSite implements ViewSite {
+
+ public abstract ViewContent getViewContent();
+
+ public abstract Context getViewContext();
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ContainerViewContent.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ContainerViewContent.java
new file mode 100644
index 00000000..bf9e6768
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ContainerViewContent.java
@@ -0,0 +1,35 @@
+package com.link_intersystems.swing.view;
+
+import java.awt.*;
+import java.util.Objects;
+
+public class ContainerViewContent implements ViewContent {
+
+ private Container container;
+ private Object contentLayoutConstraints;
+
+ private Component viewContent;
+
+ public ContainerViewContent(Container container, Object contentLayoutConstraints) {
+ this.container = Objects.requireNonNull(container);
+ this.contentLayoutConstraints = contentLayoutConstraints;
+ }
+
+ @Override
+ public void setComponent(Component viewContent) {
+ if (this.viewContent != null) {
+ container.remove(this.viewContent);
+ }
+
+ this.viewContent = viewContent;
+
+ if (this.viewContent != null) {
+ this.container.add(this.viewContent, contentLayoutConstraints);
+ }
+ }
+
+ @Override
+ public Component getParent() {
+ return container;
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/DefaultViewSite.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/DefaultViewSite.java
new file mode 100644
index 00000000..060afc39
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/DefaultViewSite.java
@@ -0,0 +1,35 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+import com.link_intersystems.util.context.DefaultContext;
+
+import static java.util.Objects.requireNonNull;
+
+public class DefaultViewSite extends AbstractViewSite {
+ private ViewContent viewContent;
+ private Context viewContext;
+
+ public DefaultViewSite(ViewContent viewContent) {
+ this(viewContent, new DefaultContext());
+ }
+
+ public DefaultViewSite(Context viewContext) {
+ this(ViewContent.nullInstance(), viewContext);
+ }
+
+ public DefaultViewSite(ViewContent viewContent, Context viewContext) {
+ this.viewContent = requireNonNull(viewContent);
+ this.viewContext = requireNonNull(viewContext);
+ }
+
+ @Override
+ public ViewContent getViewContent() {
+ return viewContent;
+ }
+
+ @Override
+ public Context getViewContext() {
+ return viewContext;
+ }
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/RootViewSite.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/RootViewSite.java
new file mode 100644
index 00000000..8fc36b91
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/RootViewSite.java
@@ -0,0 +1,17 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.swing.view.window.WindowViewContent;
+import com.link_intersystems.util.context.Context;
+import com.link_intersystems.util.context.DefaultContext;
+
+public class RootViewSite extends DefaultViewSite {
+
+ public RootViewSite() {
+ this(new DefaultContext());
+ }
+
+ public RootViewSite(Context viewContext) {
+ super(new WindowViewContent(), viewContext);
+ }
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/View.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/View.java
new file mode 100644
index 00000000..b6727bdc
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/View.java
@@ -0,0 +1,8 @@
+package com.link_intersystems.swing.view;
+
+public interface View {
+ void install(ViewSite viewSite);
+
+ void uninstall();
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewContent.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewContent.java
new file mode 100644
index 00000000..14e97eb3
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewContent.java
@@ -0,0 +1,24 @@
+package com.link_intersystems.swing.view;
+
+import java.awt.*;
+
+public interface ViewContent {
+
+ public static ViewContent nullInstance() {
+ return new ViewContent() {
+
+ @Override
+ public void setComponent(Component component) {
+ }
+
+ @Override
+ public Component getParent() {
+ return null;
+ }
+ };
+ }
+
+ void setComponent(Component component);
+
+ Component getParent();
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewSite.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewSite.java
new file mode 100644
index 00000000..c14147ac
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/ViewSite.java
@@ -0,0 +1,10 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+
+public interface ViewSite {
+
+ ViewContent getViewContent();
+ Context getViewContext();
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/action/ViewInstallAction.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/action/ViewInstallAction.java
new file mode 100644
index 00000000..6dd3bf34
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/action/ViewInstallAction.java
@@ -0,0 +1,30 @@
+package com.link_intersystems.swing.view.action;
+
+import com.link_intersystems.swing.view.View;
+import com.link_intersystems.swing.view.ViewSite;
+
+import javax.swing.*;
+import java.awt.event.ActionEvent;
+import java.util.function.Supplier;
+
+import static java.util.Objects.requireNonNull;
+
+public class ViewInstallAction extends AbstractAction {
+
+ private final Supplier viewSiteSupplier;
+ private final Supplier viewSupplier;
+
+ private View view;
+
+ public ViewInstallAction(Supplier viewSiteSupplier, Supplier viewSupplier) {
+ this.viewSiteSupplier = requireNonNull(viewSiteSupplier);
+ this.viewSupplier = requireNonNull(viewSupplier);
+ }
+
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ view = viewSupplier.get();
+ view.install(viewSiteSupplier.get());
+ }
+
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContribution.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContribution.java
new file mode 100644
index 00000000..33ccedff
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContribution.java
@@ -0,0 +1,88 @@
+package com.link_intersystems.swing.view.layout;
+
+import com.link_intersystems.swing.view.View;
+
+import java.util.*;
+
+/**
+ * A {@link ViewLayoutContribution} that keeps track of all installed views and therefore
+ * doesn't need an implementor to handle {@link #uninstall(ViewLayout)}.
+ */
+public abstract class AbstractViewLayoutContribution implements ViewLayoutContribution {
+
+ private static class ViewInstallation {
+ private ViewLayout viewLayout;
+ private String viewSiteName;
+
+ public ViewInstallation(ViewLayout viewLayout, String viewSiteName) {
+ this.viewLayout = viewLayout;
+ this.viewSiteName = viewSiteName;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ViewInstallation that = (ViewInstallation) o;
+ return Objects.equals(viewLayout, that.viewLayout) && Objects.equals(viewSiteName, that.viewSiteName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(viewLayout, viewSiteName);
+ }
+ }
+
+ private String viewLayoutId = ViewLayout.MAIN_ID;
+
+ private Map installedViews = new HashMap<>();
+
+ @Override
+ public String getViewLayoutId() {
+ return viewLayoutId;
+ }
+
+ @Override
+ public final void install(ViewLayout viewLayout) {
+ if (!getViewLayoutId().equals(viewLayout.getId())) {
+ throw new IllegalArgumentException("Can not install " + this + " in " + viewLayout);
+ }
+
+ doInstall(new ViewLayout() {
+
+ @Override
+ public String getId() {
+ return viewLayout.getId();
+ }
+
+ @Override
+ public void install(String viewSiteName, View view) {
+ viewLayout.install(viewSiteName, view);
+ installedViews.put(new ViewInstallation(viewLayout, viewSiteName), view);
+ }
+
+ @Override
+ public void remove(String viewSiteName) {
+ viewLayout.remove(viewSiteName);
+ installedViews.remove(new ViewInstallation(viewLayout, viewSiteName));
+ }
+ });
+ }
+
+ protected abstract void doInstall(ViewLayout viewLayout);
+
+ @Override
+ public final void uninstall(ViewLayout viewLayout) {
+ Set> installedViewEntries = installedViews.entrySet();
+
+ for (Map.Entry installedViewEntry : installedViewEntries) {
+ ViewInstallation viewInstallation = installedViewEntry.getKey();
+ viewInstallation.viewLayout.remove(viewInstallation.viewSiteName);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "AbstractViewLayoutContribution{" + "viewLayoutId='" + viewLayoutId + '\'' + '}';
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/DefaultViewLayout.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/DefaultViewLayout.java
new file mode 100644
index 00000000..aa0b2f85
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/DefaultViewLayout.java
@@ -0,0 +1,77 @@
+package com.link_intersystems.swing.view.layout;
+
+import com.link_intersystems.swing.view.ContainerViewContent;
+import com.link_intersystems.swing.view.DefaultViewSite;
+import com.link_intersystems.swing.view.View;
+import com.link_intersystems.swing.view.ViewSite;
+import com.link_intersystems.util.context.Context;
+
+import java.awt.*;
+import java.util.HashMap;
+import java.util.Map;
+
+import static java.util.Objects.requireNonNull;
+
+public class DefaultViewLayout implements ViewLayout {
+
+ private String id;
+ private Map layout = new HashMap<>();
+ private Map installedViews = new HashMap<>();
+ private Context viewContext;
+ private Container viewContainer;
+
+ public DefaultViewLayout(Context viewContext, Container viewContainer) {
+ this(MAIN_ID, viewContext, viewContainer);
+ }
+
+ public DefaultViewLayout(String id, Context viewContext, Container viewContainer) {
+ this.id = requireNonNull(id);
+ if (id.isBlank()) {
+ throw new IllegalArgumentException("id must not be blank");
+ }
+ this.viewContext = requireNonNull(viewContext);
+ this.viewContainer = requireNonNull(viewContainer);
+ }
+
+ @Override
+ public String getId() {
+ return id;
+ }
+
+ public void addViewSite(String name, Object layoutConstraints) {
+ ViewSite layoutViewSite = new DefaultViewSite(new ContainerViewContent(viewContainer, layoutConstraints), viewContext);
+ layout.put(requireNonNull(name), layoutViewSite);
+
+ }
+
+ @Override
+ public void install(String viewSiteName, View view) {
+ ViewSite viewSite = layout.get(viewSiteName);
+ if (viewSite == null) {
+ throw new IllegalArgumentException("No viewSite named " + viewSiteName + " existent in " + this);
+ }
+ view.install(viewSite);
+ installedViews.put(viewSiteName, view);
+ viewContainer.revalidate();
+ }
+
+ @Override
+ public void remove(String viewSiteName) {
+ View view = installedViews.get(viewSiteName);
+ if (view != null) {
+ view.uninstall();
+ viewContainer.revalidate();
+ }
+ }
+
+ public void dispose() {
+ installedViews.keySet().forEach(this::remove);
+ }
+
+ @Override
+ public String toString() {
+ return "DefaultViewLayout{" +
+ "id='" + id + '\'' +
+ '}';
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayout.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayout.java
new file mode 100644
index 00000000..c4e19b25
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayout.java
@@ -0,0 +1,14 @@
+package com.link_intersystems.swing.view.layout;
+
+import com.link_intersystems.swing.view.View;
+
+public interface ViewLayout {
+
+ public static final String MAIN_ID = "main";
+
+ public String getId();
+
+ void install(String viewSiteName, View view);
+
+ void remove(String viewSiteName);
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayoutContribution.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayoutContribution.java
new file mode 100644
index 00000000..359be4f4
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/layout/ViewLayoutContribution.java
@@ -0,0 +1,13 @@
+package com.link_intersystems.swing.view.layout;
+
+
+public interface ViewLayoutContribution {
+
+ default public String getViewLayoutId() {
+ return ViewLayout.MAIN_ID;
+ }
+
+ void install(ViewLayout viewLayout);
+
+ void uninstall(ViewLayout viewLayout);
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/menu/MenuContribution.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/menu/MenuContribution.java
new file mode 100644
index 00000000..49c43d16
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/menu/MenuContribution.java
@@ -0,0 +1,11 @@
+package com.link_intersystems.swing.view.menu;
+
+import com.link_intersystems.util.context.Context;
+
+import javax.swing.*;
+
+public interface MenuContribution {
+ String getMenuPath();
+
+ Action getAction(Context context);
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/SubWindowViewContent.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/SubWindowViewContent.java
new file mode 100644
index 00000000..d3730a23
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/SubWindowViewContent.java
@@ -0,0 +1,19 @@
+package com.link_intersystems.swing.view.window;
+
+import java.awt.*;
+
+import static java.util.Objects.requireNonNull;
+
+public class SubWindowViewContent extends WindowViewContent {
+
+ private Component parent;
+
+ public SubWindowViewContent(Component parent) {
+ this.parent = requireNonNull(parent);
+ }
+
+ @Override
+ public Component getParent() {
+ return parent;
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowCloseAction.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowCloseAction.java
new file mode 100644
index 00000000..d69e9d93
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowCloseAction.java
@@ -0,0 +1,28 @@
+package com.link_intersystems.swing.view.window;
+
+import com.link_intersystems.swing.action.ActionTrigger;
+import com.link_intersystems.swing.view.View;
+
+import javax.swing.*;
+import java.awt.event.WindowEvent;
+
+public interface WindowCloseAction {
+
+ public static final WindowCloseAction DEFAULT = new WindowCloseAction() {
+ };
+
+ public static WindowCloseAction actionAdapter(Action action) {
+ return new WindowCloseAction() {
+ @Override
+ public void actionPerformed(WindowEvent windowEvent, View view) {
+ ActionTrigger.performAction(windowEvent.getSource(), action);
+ WindowCloseAction.super.actionPerformed(windowEvent, view);
+ }
+ };
+ }
+
+ default
+ public void actionPerformed(WindowEvent windowEvent, View view) {
+ view.uninstall();
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowView.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowView.java
new file mode 100644
index 00000000..a912a508
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowView.java
@@ -0,0 +1,97 @@
+package com.link_intersystems.swing.view.window;
+
+import com.link_intersystems.events.awt.WindowEventMethod;
+import com.link_intersystems.swing.view.AbstractView;
+import com.link_intersystems.swing.view.ViewContent;
+import com.link_intersystems.swing.view.ViewSite;
+import com.link_intersystems.util.context.Context;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowEvent;
+import java.awt.event.WindowListener;
+import java.util.Optional;
+
+import static java.util.Objects.requireNonNull;
+import static javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE;
+
+public abstract class WindowView extends AbstractView {
+
+ public static final String DEFAULT_CLOSE_ACTION = WindowView.class.getName() + ".closeAction";
+ private Window window;
+
+ private int closeOperation;
+ private WindowListener closeHandler;
+
+ public WindowView() {
+ setDefaultCloseOperation(DO_NOTHING_ON_CLOSE);
+ setWindowCloseMethod(WindowEventMethod.CLOSING);
+ }
+
+ @Override
+ protected void doInstall(ViewSite viewSite) {
+ window = createWindow(viewSite);
+ setDefaultCloseOperation(window, closeOperation);
+ window.addWindowListener(closeHandler);
+ ViewContent viewContent = viewSite.getViewContent();
+ viewContent.setComponent(window);
+ }
+
+ /**
+ * @param windowConstantsValue a value of {@link WindowConstants}.
+ */
+ public void setDefaultCloseOperation(int windowConstantsValue) {
+ setDefaultCloseOperation(window, windowConstantsValue);
+ }
+
+ /**
+ * Sets the event method that will cause a {@link WindowCloseAction}, registered to the {@link Context}
+ * with the {@link #DEFAULT_CLOSE_ACTION} name, to be performed.
+ *
+ * @param windowCloseMethod a constant of {@link WindowEventMethod}. Default is #{@link WindowEventMethod#CLOSING}.
+ */
+ public void setWindowCloseMethod(WindowEventMethod windowCloseMethod) {
+ requireNonNull(windowCloseMethod);
+
+ if (window != null) {
+ window.removeWindowListener(closeHandler);
+ }
+
+ closeHandler = windowCloseMethod.listener((e) -> onCloseWindow(e, getViewSite()));
+
+ if (window != null) {
+ window.addWindowListener(closeHandler);
+ }
+ }
+
+ protected void setDefaultCloseOperation(Window window, int closeOperation) {
+ if (window instanceof JFrame) {
+ JFrame frame = (JFrame) window;
+ frame.setDefaultCloseOperation(closeOperation);
+ } else if (window instanceof JDialog) {
+ JDialog dialog = (JDialog) window;
+ dialog.setDefaultCloseOperation(closeOperation);
+ }
+ }
+
+ protected abstract Window createWindow(ViewSite viewSite);
+
+ @Override
+ protected void doUninstall(ViewSite viewSite) {
+ super.doUninstall(viewSite);
+
+ window.removeWindowListener(closeHandler);
+ closeHandler = null;
+
+ window.dispose();
+ window = null;
+ }
+
+ protected void onCloseWindow(WindowEvent windowEvent, ViewSite viewSite) {
+ Context viewContext = viewSite.getViewContext();
+
+ Optional windowCloseAction = viewContext.find(WindowCloseAction.class, DEFAULT_CLOSE_ACTION);
+
+ windowCloseAction.orElse(WindowCloseAction.DEFAULT).actionPerformed(windowEvent, this);
+ }
+}
diff --git a/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowViewContent.java b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowViewContent.java
new file mode 100644
index 00000000..656343a0
--- /dev/null
+++ b/lis-commons-swing-view/src/main/java/com/link_intersystems/swing/view/window/WindowViewContent.java
@@ -0,0 +1,28 @@
+package com.link_intersystems.swing.view.window;
+
+import com.link_intersystems.swing.view.ViewContent;
+
+import java.awt.*;
+
+public class WindowViewContent implements ViewContent {
+
+ private Component component;
+
+ @Override
+ public void setComponent(Component component) {
+ if(this.component != null){
+ this.component.setVisible(false);
+ }
+
+ this.component = component;
+
+ if(this.component != null){
+ this.component.setVisible(true);
+ }
+ }
+
+ @Override
+ public Component getParent() {
+ return null;
+ }
+}
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/AbstractViewTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/AbstractViewTest.java
new file mode 100644
index 00000000..c80472f5
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/AbstractViewTest.java
@@ -0,0 +1,47 @@
+package com.link_intersystems.swing.view;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class AbstractViewTest {
+
+ private AbstractView abstractView;
+ private ViewSiteMock viewSiteMock;
+
+ @Test
+ void install() {
+ abstractView = spy(AbstractView.class);
+
+ assertNull(abstractView.getContextDsl());
+
+ viewSiteMock = new ViewSiteMock();
+ abstractView.install(viewSiteMock);
+
+ verify(abstractView, times(1)).doInstall(viewSiteMock);
+
+ assertEquals(viewSiteMock, abstractView.getViewSite());
+ assertNotNull(abstractView.getContextDsl());
+ }
+
+ @Test
+ void uninstall() {
+ install();
+ reset(abstractView);
+
+ abstractView.uninstall();
+
+ verify(abstractView, times(1)).doUninstall(viewSiteMock);
+ }
+
+ @Test
+ void createSubViewSite() {
+ install();
+
+ ViewContent content = mock(ViewContent.class);
+ ViewSite subViewSite = abstractView.createSubViewSite(content);
+
+ assertInstanceOf(DefaultViewSite.class, subViewSite);
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ContainerViewContentTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ContainerViewContentTest.java
new file mode 100644
index 00000000..2681b0fd
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ContainerViewContentTest.java
@@ -0,0 +1,56 @@
+package com.link_intersystems.swing.view;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.swing.*;
+import java.awt.*;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.mockito.Mockito.*;
+
+class ContainerViewContentTest {
+
+ private JPanel container;
+ private LayoutManager2 layoutManager;
+ private ContainerViewContent containerViewContent;
+
+ @BeforeEach
+ void setUp() {
+ container = new JPanel();
+ layoutManager = mock(LayoutManager2.class);
+ container.setLayout(layoutManager);
+
+ containerViewContent = new ContainerViewContent(container, BorderLayout.SOUTH);
+ }
+
+
+ @Test
+ void getParent() {
+
+ assertEquals(container, containerViewContent.getParent());
+ }
+
+ @Test
+ void setComponent() {
+ JLabel viewContent = new JLabel();
+ containerViewContent.setComponent(viewContent);
+
+ assertEquals(1, container.getComponents().length);
+ assertEquals(viewContent, container.getComponents()[0]);
+ verify(layoutManager, times(1)).addLayoutComponent(viewContent, BorderLayout.SOUTH);
+ }
+
+ @Test
+ void resetComponent() {
+ setComponent();
+
+ JLabel anotherLabel = new JLabel();
+ containerViewContent.setComponent(anotherLabel);
+
+ assertEquals(1, container.getComponents().length);
+ assertEquals(anotherLabel, container.getComponents()[0]);
+
+ verify(layoutManager, times(1)).addLayoutComponent(anotherLabel, BorderLayout.SOUTH);
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/DefaultViewSiteTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/DefaultViewSiteTest.java
new file mode 100644
index 00000000..9f69f822
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/DefaultViewSiteTest.java
@@ -0,0 +1,40 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.mock;
+
+class DefaultViewSiteTest {
+
+ @Test
+ void contentAndContext() {
+ ViewContent viewContent = mock(ViewContent.class);
+ Context context = mock(Context.class);
+ DefaultViewSite defaultViewSite = new DefaultViewSite(viewContent, context);
+
+ assertEquals(viewContent, defaultViewSite.getViewContent());
+ assertEquals(context, defaultViewSite.getViewContext());
+ }
+
+ @Test
+ void contentOnly() {
+ ViewContent viewContent = mock(ViewContent.class);
+
+ DefaultViewSite defaultViewSite = new DefaultViewSite(viewContent);
+
+ assertEquals(viewContent, defaultViewSite.getViewContent());
+ assertNotNull(defaultViewSite.getViewContext());
+ }
+
+ @Test
+ void contextOnly() {
+ Context context = mock(Context.class);
+
+ DefaultViewSite defaultViewSite = new DefaultViewSite(context);
+
+ assertEquals(context, defaultViewSite.getViewContext());
+ assertNotNull(defaultViewSite.getViewContent());
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ViewSiteMock.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ViewSiteMock.java
new file mode 100644
index 00000000..3d905b7b
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/ViewSiteMock.java
@@ -0,0 +1,49 @@
+package com.link_intersystems.swing.view;
+
+import com.link_intersystems.util.context.Context;
+import com.link_intersystems.util.context.DefaultContext;
+
+import javax.swing.*;
+import java.awt.*;
+
+public class ViewSiteMock implements ViewSite {
+
+ private ViewContent viewContent = new ViewContent() {
+ @Override
+ public void setComponent(Component component) {
+ content = component;
+ }
+
+ @Override
+ public Component getParent() {
+ return parent;
+ }
+ };
+
+ private DefaultContext context = new DefaultContext();
+
+ private Component content;
+ private Component parent;
+
+ public Component getContent() {
+ return content;
+ }
+
+ public void setParent(Component parent) {
+ this.parent = parent;
+ }
+
+ @Override
+ public ViewContent getViewContent() {
+ return viewContent;
+ }
+
+ @Override
+ public Context getViewContext() {
+ return context;
+ }
+
+ public void addContextObject(Class type, String name, T contextObject) {
+ context.put(type, name, contextObject);
+ }
+}
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContributionTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContributionTest.java
new file mode 100644
index 00000000..77d38e6d
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/AbstractViewLayoutContributionTest.java
@@ -0,0 +1,96 @@
+package com.link_intersystems.swing.view.layout;
+
+import com.link_intersystems.swing.view.View;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+import org.mockito.stubbing.Answer;
+
+import static org.junit.jupiter.api.Assertions.*;
+import static org.mockito.Mockito.*;
+
+class AbstractViewLayoutContributionTest {
+
+ private AbstractViewLayoutContribution viewLayoutContribution;
+ private ViewLayout viewLayout;
+ private View view;
+
+ @BeforeEach
+ void setUp() {
+ viewLayoutContribution = spy(AbstractViewLayoutContribution.class);
+
+ viewLayout = spy(ViewLayout.class);
+ doReturn(ViewLayout.MAIN_ID).when(viewLayout).getId();
+
+ view = mock(View.class);
+ }
+
+ @Test
+ void contributeToWrongViewLayout() {
+ when(viewLayout.getId()).thenReturn("UNKNOWN");
+
+ assertThrows(IllegalArgumentException.class, () -> viewLayoutContribution.install(viewLayout));
+ }
+
+ @Test
+ void install() {
+ doAnswer((Answer) invocation -> {
+ ViewLayout layout = invocation.getArgument(0, ViewLayout.class);
+ assertEquals(ViewLayout.MAIN_ID, layout.getId());
+ layout.install("A", view);
+ return null;
+ }).when(viewLayoutContribution).doInstall(Mockito.any(ViewLayout.class));
+
+ viewLayoutContribution.install(viewLayout);
+
+ verify(viewLayout, times(1)).install("A", view);
+ }
+
+ @Test
+ void installMultipleView() {
+ View view2 = mock(View.class);
+
+ doAnswer((Answer) invocation -> {
+ ViewLayout layout = invocation.getArgument(0, ViewLayout.class);
+ layout.install("A", view);
+ layout.install("B", view2);
+ return null;
+ }).when(viewLayoutContribution).doInstall(Mockito.any(ViewLayout.class));
+
+ viewLayoutContribution.install(viewLayout);
+
+ verify(viewLayout, times(1)).install("A", view);
+ verify(viewLayout, times(1)).install("B", view2);
+ }
+
+ @Test
+ void removePreviousViewOnInstall() {
+ install();
+
+ doAnswer((Answer) invocation -> {
+ ViewLayout layout = invocation.getArgument(0, ViewLayout.class);
+ layout.remove("A");
+ layout.install("B", view);
+ return null;
+ }).when(viewLayoutContribution).doInstall(Mockito.any(ViewLayout.class));
+
+ viewLayoutContribution.install(viewLayout);
+
+ verify(viewLayout, times(1)).install("B", view);
+ }
+
+ @Test
+ void uninstall() {
+ install();
+
+ viewLayoutContribution.uninstall(viewLayout);
+
+ verify(viewLayout, times(1)).remove("A");
+ }
+
+ @Test
+ void testToString() {
+
+ assertNotNull(viewLayoutContribution.toString());
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/DefaultViewLayoutTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/DefaultViewLayoutTest.java
new file mode 100644
index 00000000..beab7280
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/layout/DefaultViewLayoutTest.java
@@ -0,0 +1,58 @@
+package com.link_intersystems.swing.view.layout;
+
+import com.link_intersystems.swing.view.View;
+import com.link_intersystems.swing.view.ViewSite;
+import com.link_intersystems.util.context.DefaultContext;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.mockito.Mockito;
+
+import java.awt.*;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.Mockito.*;
+
+class DefaultViewLayoutTest {
+
+ private DefaultViewLayout defaultViewLayout;
+ private Container container;
+ private DefaultContext context;
+ private View view;
+
+ @BeforeEach
+ void setUp() {
+ context = new DefaultContext();
+ container = mock(Container.class);
+ defaultViewLayout = new DefaultViewLayout(context, container);
+
+ defaultViewLayout.addViewSite("N", BorderLayout.NORTH);
+ defaultViewLayout.addViewSite("S", BorderLayout.SOUTH);
+ defaultViewLayout.addViewSite("C", BorderLayout.CENTER);
+
+ view = mock(View.class);
+ }
+
+ @Test
+ void installUnknownViewSite() {
+ assertThrows(IllegalArgumentException.class, () -> defaultViewLayout.install("NORTH", view));
+ }
+
+ @Test
+ void install() {
+ defaultViewLayout.install("N", view);
+
+ verify(view, times(1)).install(Mockito.any(ViewSite.class));
+ verify(container, times(1)).revalidate();
+ }
+
+ @Test
+ void dispose() {
+ install();
+ reset(container);
+
+ defaultViewLayout.dispose();
+
+ verify(view, times(1)).uninstall();
+ verify(container, times(1)).revalidate();
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowCloseActionTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowCloseActionTest.java
new file mode 100644
index 00000000..55668631
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowCloseActionTest.java
@@ -0,0 +1,30 @@
+package com.link_intersystems.swing.view.window;
+
+import com.link_intersystems.beans.mockito.BeanMatchers;
+import com.link_intersystems.swing.view.View;
+import org.junit.jupiter.api.Test;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.ActionEvent;
+import java.awt.event.WindowEvent;
+
+import static org.mockito.Mockito.*;
+
+class WindowCloseActionTest {
+
+ @Test
+ void actionAdapter() {
+ Action action = mock(Action.class);
+ Window window = mock(Window.class);
+ View view = mock(View.class);
+
+ WindowCloseAction windowCloseAction = WindowCloseAction.actionAdapter(action);
+
+ windowCloseAction.actionPerformed(new WindowEvent(window, WindowEvent.WINDOW_CLOSING), view);
+
+ ActionEvent actionEvent = new ActionEvent(window, ActionEvent.ACTION_PERFORMED, "");
+
+ verify(action, times(1)).actionPerformed(BeanMatchers.propertiesEqual(actionEvent));
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewContentTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewContentTest.java
new file mode 100644
index 00000000..2ac72ad6
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewContentTest.java
@@ -0,0 +1,45 @@
+package com.link_intersystems.swing.view.window;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.awt.*;
+
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.Mockito.*;
+
+class WindowViewContentTest {
+
+ private WindowViewContent windowViewContent;
+
+ @BeforeEach
+ void setUp() {
+ windowViewContent = new WindowViewContent();
+
+
+ }
+
+ @Test
+ void setComponent() {
+ Component component = mock(Component.class);
+ windowViewContent.setComponent(component);
+
+ verify(component, times(1)).setVisible(true);
+ }
+
+ @Test
+ void resetComponent() {
+ setComponent();
+
+ Component component = mock(Component.class);
+ windowViewContent.setComponent(component);
+
+ verify(component, times(1)).setVisible(true);
+ }
+
+ @Test
+ void getParent() {
+
+ assertNull(windowViewContent.getParent());
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewTest.java b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewTest.java
new file mode 100644
index 00000000..5989cb33
--- /dev/null
+++ b/lis-commons-swing-view/src/test/java/com/link_intersystems/swing/view/window/WindowViewTest.java
@@ -0,0 +1,75 @@
+package com.link_intersystems.swing.view.window;
+
+import com.link_intersystems.events.awt.WindowEventMethod;
+import com.link_intersystems.swing.view.View;
+import com.link_intersystems.swing.view.ViewSite;
+import com.link_intersystems.swing.view.ViewSiteMock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import javax.swing.*;
+import java.awt.*;
+import java.awt.event.WindowEvent;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.*;
+
+class WindowViewTest {
+
+ private JFrame frame;
+ private WindowView windowView;
+ private ViewSiteMock viewSiteMock;
+
+ @BeforeEach
+ void setUp() {
+ frame = new JFrame();
+ windowView = new WindowView() {
+ @Override
+ protected Window createWindow(ViewSite viewSite) {
+
+ return frame;
+ }
+ };
+
+ viewSiteMock = new ViewSiteMock();
+ }
+
+ @Test
+ void install() {
+ windowView.install(viewSiteMock);
+
+ assertEquals(frame, viewSiteMock.getContent());
+ }
+
+ @Test
+ void uninstall() {
+ install();
+
+ WindowCloseAction closeAction = spy(WindowCloseAction.class);
+ viewSiteMock.addContextObject(WindowCloseAction.class, WindowView.DEFAULT_CLOSE_ACTION, closeAction);
+
+ frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSING));
+
+ verify(closeAction, times(1)).actionPerformed(any(WindowEvent.class), any(View.class));
+
+ assertNull(viewSiteMock.getContent(), "frame uninstalled");
+ }
+
+ @Test
+ void setWindowCloseMethod() {
+ install();
+
+ windowView.setWindowCloseMethod(WindowEventMethod.CLOSED);
+
+ WindowCloseAction closeAction = spy(WindowCloseAction.class);
+ viewSiteMock.addContextObject(WindowCloseAction.class, WindowView.DEFAULT_CLOSE_ACTION, closeAction);
+
+ frame.dispatchEvent(new WindowEvent(frame, WindowEvent.WINDOW_CLOSED));
+
+ verify(closeAction, times(1)).actionPerformed(any(WindowEvent.class), any(View.class));
+
+ assertNull(viewSiteMock.getContent(), "frame uninstalled");
+ }
+}
\ No newline at end of file
diff --git a/lis-commons-swing/pom.xml b/lis-commons-swing/pom.xml
index 8c0157f0..e934efb7 100644
--- a/lis-commons-swing/pom.xml
+++ b/lis-commons-swing/pom.xml
@@ -1,5 +1,6 @@
-
+
lis-commons
com.link-intersystems.commons
@@ -10,7 +11,8 @@
lis-commons-swing
LIS Commons Swing
- Link Intersystems Commons Swing (lis-commons-swing) provides support for Java swing related tasks.
+ Link Intersystems Commons Swing (lis-commons-swing) provides support for Java swing related tasks.
+
@@ -33,8 +35,7 @@
com.link-intersystems.commons
- lis-commons-beans
- test-jar
+ lis-commons-beans-mockito
test
diff --git a/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/ActionTrigger.java b/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/ActionTrigger.java
index b3d5d4ea..023c8107 100644
--- a/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/ActionTrigger.java
+++ b/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/ActionTrigger.java
@@ -19,7 +19,8 @@ public static void performAction(Object actionEventSource, ActionListener action
}
public static void performAction(Object actionEventSource, ActionListener actionListener, String command, int actionId) {
- actionListener.actionPerformed(new ActionEvent(actionEventSource, actionId, command));
+ ActionEvent actionEvent = new ActionEvent(actionEventSource, actionId, command);
+ actionListener.actionPerformed(actionEvent);
}
private Object actionEventSource;
diff --git a/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/spi/ServiceLoaderAction.java b/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/spi/ServiceLoaderAction.java
new file mode 100644
index 00000000..1975ddf3
--- /dev/null
+++ b/lis-commons-swing/src/main/java/com/link_intersystems/swing/action/spi/ServiceLoaderAction.java
@@ -0,0 +1,25 @@
+package com.link_intersystems.swing.action.spi;
+
+import com.link_intersystems.swing.action.concurrent.DefaultTaskAction;
+import com.link_intersystems.util.concurrent.task.TaskProgress;
+
+import java.util.List;
+import java.util.ServiceLoader;
+import java.util.stream.Collectors;
+
+import static java.util.Objects.requireNonNull;
+
+public class ServiceLoaderAction extends DefaultTaskAction, Void> {
+
+ private Class serviceType;
+
+ public ServiceLoaderAction(Class serviceType) {
+ this.serviceType = requireNonNull(serviceType);
+ }
+
+ @Override
+ protected List doInBackground(TaskProgress taskProgress) {
+ return ServiceLoader.load(serviceType).stream().map(ServiceLoader.Provider::get).collect(Collectors.toList());
+ }
+
+}
diff --git a/lis-commons-swing/src/test/java/com/link_intersystems/swing/table/ListTableModelTest.java b/lis-commons-swing/src/test/java/com/link_intersystems/swing/table/ListTableModelTest.java
index 618a7bcd..63ce6b91 100644
--- a/lis-commons-swing/src/test/java/com/link_intersystems/swing/table/ListTableModelTest.java
+++ b/lis-commons-swing/src/test/java/com/link_intersystems/swing/table/ListTableModelTest.java
@@ -7,7 +7,7 @@
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
-import static com.link_intersystems.mockito.beans.BeanMatchers.*;
+import static com.link_intersystems.beans.mockito.BeanMatchers.*;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
diff --git a/lis-commons-util-context/pom.xml b/lis-commons-util-context/pom.xml
new file mode 100644
index 00000000..738b8da9
--- /dev/null
+++ b/lis-commons-util-context/pom.xml
@@ -0,0 +1,14 @@
+
+
+ 4.0.0
+
+ com.link-intersystems.commons
+ lis-commons
+ 1.9.7-SNAPSHOT
+
+
+ lis-commons-util-context
+
+
\ No newline at end of file
diff --git a/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/AbstractContext.java b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/AbstractContext.java
new file mode 100644
index 00000000..ef00955d
--- /dev/null
+++ b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/AbstractContext.java
@@ -0,0 +1,151 @@
+package com.link_intersystems.util.context;
+
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import static java.util.Objects.requireNonNull;
+
+public abstract class AbstractContext implements Context {
+
+ protected abstract QualifiedObject> removeInstance(ObjectQualifier> objectQualifier);
+
+ protected abstract QualifiedObject getInstance(ObjectQualifier super T> objectQualifier);
+
+ @SuppressWarnings("CanBeFinal")
+ private static class ViewContextListenerRegistration {
+
+ private ObjectQualifier super T> objectQualifier;
+ private ContextListener contextListener;
+
+ public ViewContextListenerRegistration(ObjectQualifier super T> objectQualifier, ContextListener contextListener) {
+ this.objectQualifier = requireNonNull(objectQualifier);
+ this.contextListener = requireNonNull(contextListener);
+ }
+
+ @SuppressWarnings("unchecked")
+ public ContextListener cast(ObjectQualifier super T> objectQualifier) {
+ if (accept(objectQualifier)) {
+ return (ContextListener) contextListener;
+ }
+ return null;
+ }
+
+ public boolean accept(ObjectQualifier super T> objectQualifier) {
+ return this.objectQualifier.equals(objectQualifier);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ViewContextListenerRegistration> that = (ViewContextListenerRegistration>) o;
+ return Objects.equals(objectQualifier, that.objectQualifier) && Objects.equals(contextListener, that.contextListener);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(objectQualifier, contextListener);
+ }
+ }
+
+ private List> viewContextListenerRegistrations = new ArrayList<>();
+
+ @Override
+ public void addViewContextListener(ObjectQualifier super T> objectQualifier, ContextListener contextListener) {
+ ViewContextListenerRegistration listenerRegistration = new ViewContextListenerRegistration<>(objectQualifier, contextListener);
+ viewContextListenerRegistrations.add(listenerRegistration);
+
+ onViewContextListenerAdded(objectQualifier, contextListener);
+ }
+
+ protected void onViewContextListenerAdded(ObjectQualifier super T> objectQualifier, ContextListener contextListener) {
+ QualifiedObject registration = getInstance(objectQualifier);
+ if (registration != null) {
+ T object = registration.getObject();
+ contextListener.added(this, object);
+ }
+ }
+
+
+ @Override
+ public void removeViewContextListener(ObjectQualifier super T> objectQualifier, ContextListener contextListener) {
+ ViewContextListenerRegistration listenerRegistration = new ViewContextListenerRegistration<>(objectQualifier, contextListener);
+ if (viewContextListenerRegistrations.remove(listenerRegistration)) {
+ onViewContextListenerRemoved(listenerRegistration.contextListener);
+ }
+ }
+
+ protected void onViewContextListenerRemoved(ContextListener contextListener) {
+ }
+
+ @Override
+ public void put(ObjectQualifier super T> objectQualifier, O object) {
+ QualifiedObject qualifiedObject = new QualifiedObject<>(objectQualifier, object);
+
+ if (putInstance(qualifiedObject)) {
+ onPutInstance(qualifiedObject);
+ return;
+ }
+
+ throw new DuplicateObjectException(objectQualifier);
+ }
+
+ protected abstract boolean putInstance(QualifiedObject> qualifiedObject);
+
+
+ protected void onPutInstance(QualifiedObject qualifiedObject) {
+ for (ViewContextListenerRegistration> listenerRegistration : viewContextListenerRegistrations) {
+ ObjectQualifier super T> objectQualifier = qualifiedObject.getQualifier();
+ ContextListener contextListener = listenerRegistration.cast(objectQualifier);
+ if (contextListener != null) {
+ T object = qualifiedObject.getObject();
+ contextListener.added(this, object);
+ }
+ }
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public T get(ObjectQualifier objectQualifier) {
+ if (objectQualifier == null) {
+ return null;
+ }
+
+ QualifiedObject> qualifiedObject = getInstance(objectQualifier);
+
+ if (qualifiedObject == null) {
+ throw new NoSuchObjectException(objectQualifier);
+ }
+
+ return (T) qualifiedObject.getObject();
+ }
+
+ @Override
+ public boolean remove(ObjectQualifier> objectQualifier) {
+ QualifiedObject> removedInstance = removeInstance(objectQualifier);
+
+ if (removedInstance != null) {
+ onRemoveInstance(removedInstance);
+ }
+
+ return removedInstance != null;
+ }
+
+ protected void onRemoveInstance(QualifiedObject removedRegistration) {
+ for (ViewContextListenerRegistration> listenerRegistration : viewContextListenerRegistrations) {
+ ObjectQualifier super T> objectQualifier = removedRegistration.getQualifier();
+ ContextListener contextListener = listenerRegistration.cast(objectQualifier);
+ if (contextListener != null) {
+ T removedObject = removedRegistration.getObject();
+ contextListener.removed(this, removedObject);
+ }
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getClass().getName() + "[" + Integer.toHexString(System.identityHashCode(this)) + "]";
+ }
+}
diff --git a/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/Context.java b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/Context.java
new file mode 100644
index 00000000..d32e99b2
--- /dev/null
+++ b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/Context.java
@@ -0,0 +1,117 @@
+package com.link_intersystems.util.context;
+
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+public interface Context {
+
+ default void addViewContextListener(Class super T> type, ContextListener contextListener) {
+ addViewContextListener(type, null, contextListener);
+ }
+
+ default void addViewContextListener(Class super T> type, String name, ContextListener contextListener) {
+ ObjectQualifier super T> objectQualifier = new ObjectQualifier<>(type, name);
+
+ addViewContextListener(objectQualifier, contextListener);
+ }
+
+ void addViewContextListener(ObjectQualifier super T> objectQualifier, ContextListener contextListener);
+
+ default void removeViewContextListener(Class super T> type, ContextListener contextListener) {
+ removeViewContextListener(type, null, contextListener);
+ }
+
+ default void removeViewContextListener(Class super T> type, String name, ContextListener contextListener) {
+ ObjectQualifier super T> objectQualifier = new ObjectQualifier<>(type, name);
+ removeViewContextListener(objectQualifier, contextListener);
+ }
+
+ void removeViewContextListener(ObjectQualifier super T> objectQualifier, ContextListener contextListener);
+
+ default boolean contains(Class> type) {
+ return contains(type, null);
+ }
+
+ default boolean contains(Class> type, String name) {
+ return contains(new ObjectQualifier<>(type, name));
+ }
+
+ boolean contains(ObjectQualifier> objectQualifier);
+
+ default void ifContains(Class type, Consumer objectConsumer) {
+ ifContains(type, null, objectConsumer);
+ }
+
+ default void ifContains(Class type, String name, Consumer objectConsumer) {
+ ifContains(new ObjectQualifier<>(type, name), objectConsumer);
+ }
+
+ default void ifContains(ObjectQualifier objectQualifier, Consumer objectConsumer) {
+ if (contains(objectQualifier)) {
+ T object = get(objectQualifier);
+ objectConsumer.accept(object);
+ }
+ }
+
+ default Optional find(Class type) throws ContextObjectException {
+ return find(type, null);
+ }
+
+ default Optional find(Class type, String name) throws ContextObjectException {
+ return find(new ObjectQualifier<>(type, name));
+ }
+
+ default Optional find(ObjectQualifier objectQualifier) throws ContextObjectException {
+ if (contains(objectQualifier)) {
+ return Optional.of(get(objectQualifier));
+ }
+ return Optional.empty();
+ }
+
+ default T get(Class type) throws ContextObjectException {
+ return get(type, null);
+ }
+
+ default T get(Class type, String name) throws ContextObjectException {
+ return get(new ObjectQualifier<>(type, name));
+ }
+
+ T get(ObjectQualifier objectQualifier) throws ContextObjectException;
+
+ default public Supplier getSupplier(Class type) {
+ return getSupplier(type, null);
+ }
+
+ default public Supplier getSupplier(Class type, String name) {
+ return getSupplier(new ObjectQualifier<>(type, name));
+ }
+
+ default public Supplier getSupplier(ObjectQualifier objectQualifier) {
+ return () -> get(objectQualifier);
+ }
+
+ default boolean remove(Class super T> type) {
+ return remove(type, null);
+ }
+
+ default boolean remove(Class super T> type, String name) {
+ ObjectQualifier super T> objectQualifier = new ObjectQualifier<>(type, name);
+ return remove(objectQualifier);
+ }
+
+ boolean remove(ObjectQualifier> objectQualifier);
+
+ default void put(Class type, O object) throws ContextObjectException {
+ put(type, null, object);
+ }
+
+ default void put(Class super T> type, String name, O object) throws ContextObjectException {
+ put(new ObjectQualifier<>(type, name), object);
+ }
+
+ void put(ObjectQualifier super T> objectQualifier, O object) throws ContextObjectException;
+
+ public Stream> stream();
+}
diff --git a/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextListener.java b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextListener.java
new file mode 100644
index 00000000..52d5b3f0
--- /dev/null
+++ b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextListener.java
@@ -0,0 +1,11 @@
+package com.link_intersystems.util.context;
+
+import java.util.EventListener;
+
+public interface ContextListener extends EventListener {
+
+ public void added(Context context, T instance);
+
+ public void removed(Context context, T instance);
+
+}
diff --git a/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextObjectException.java b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextObjectException.java
new file mode 100644
index 00000000..dac3470a
--- /dev/null
+++ b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/ContextObjectException.java
@@ -0,0 +1,15 @@
+package com.link_intersystems.util.context;
+
+import static java.util.Objects.requireNonNull;
+
+public class ContextObjectException extends RuntimeException {
+ private final ObjectQualifier> objectQualifier;
+
+ public ContextObjectException(ObjectQualifier> objectQualifier) {
+ this.objectQualifier = requireNonNull(objectQualifier);
+ }
+
+ public ObjectQualifier> getQualifier() {
+ return objectQualifier;
+ }
+}
diff --git a/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/DefaultContext.java b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/DefaultContext.java
new file mode 100644
index 00000000..421769d1
--- /dev/null
+++ b/lis-commons-util-context/src/main/java/com/link_intersystems/util/context/DefaultContext.java
@@ -0,0 +1,69 @@
+package com.link_intersystems.util.context;
+
+
+import java.util.Collection;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Supplier;
+import java.util.stream.Stream;
+
+public class DefaultContext extends AbstractContext implements AutoCloseable {
+
+ private Map, QualifiedObject>> qualifiedInstances;
+
+ public DefaultContext() {
+ this(ConcurrentHashMap::new);
+ }
+
+ public DefaultContext(Supplier