diff --git a/.travis.yml b/.travis.yml index 1182ad77e..c8c4e230b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,16 +1,20 @@ language: java -sudo: required +sudo: false jdk: - oraclejdk8 install: true +addons: + apt: + packages: + - oracle-java8-installer + before_install: - export DISPLAY=:99.0 - sh -e /etc/init.d/xvfb start -- sudo apt-get update && sudo apt-get install oracle-java8-installer - java -version env: @@ -19,4 +23,5 @@ env: - secure: Bm0zIwBo4saIBlfr9YDvNqd8UN50FeT2hZyum3RFDmJqaXiUqwxw7sCz/lRjUwTfQrvGJXQ1I8le7zE4OVQdvpn4/LwwTJIjLY2jZNjzs4mZhkHzsM5IcGcL3lukR6soVYrGloQwmw63Okw2kZxces+1fveisPIKDlVaU1RTtMQ= after_success: - - "[[ $TRAVIS_BRANCH == \"develop\" ]] && { python addServer.py; mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-archetype' -am -DskipTests=true --settings ~/.m2/mySettings.xml; };" + - chmod ugo+x travis_snapshot_release.sh + - ./travis_snapshot_release.sh diff --git a/README.md b/README.md index 0c658683f..957f7a269 100644 --- a/README.md +++ b/README.md @@ -11,15 +11,33 @@ __MVVM__ is the enhanced version of the [Presentation Model](http://martinfowler ### Maven dependency### #### Stable Release + +This is the stable release that can be used in production. + +```xml + + de.saxsys + mvvmfx + 1.4.1 + ``` + +#### Bugfix Development Snapshot + +Here we make bug fixes for the current stable release. + +```xml de.saxsys mvvmfx - 1.4.0 + 1.4.2-SNAPSHOT ``` #### Development Snapshot + +Here we develop new features. This release is unstable and shouldn't be used in production. + ``` de.saxsys diff --git a/examples/books-example/pom.xml b/examples/books-example/pom.xml index cf3a6355a..6dfa7867f 100644 --- a/examples/books-example/pom.xml +++ b/examples/books-example/pom.xml @@ -5,7 +5,7 @@ de.saxsys.mvvmfx examples - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/contacts-example/pom.xml b/examples/contacts-example/pom.xml index 1bdedce32..ad6855b22 100644 --- a/examples/contacts-example/pom.xml +++ b/examples/contacts-example/pom.xml @@ -6,7 +6,7 @@ de.saxsys.mvvmfx examples - 1.4.0 + 1.4.1 contacts-example diff --git a/examples/mini-examples/fx-root-example/pom.xml b/examples/mini-examples/fx-root-example/pom.xml index de4fcb36e..aeb65e31c 100644 --- a/examples/mini-examples/fx-root-example/pom.xml +++ b/examples/mini-examples/fx-root-example/pom.xml @@ -5,7 +5,7 @@ mini-examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/mini-examples/helloworld-without-fxml/pom.xml b/examples/mini-examples/helloworld-without-fxml/pom.xml index 3213f732c..96c8c4f10 100644 --- a/examples/mini-examples/helloworld-without-fxml/pom.xml +++ b/examples/mini-examples/helloworld-without-fxml/pom.xml @@ -5,7 +5,7 @@ mini-examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/mini-examples/helloworld/pom.xml b/examples/mini-examples/helloworld/pom.xml index a15606fc1..afb2ba6ae 100644 --- a/examples/mini-examples/helloworld/pom.xml +++ b/examples/mini-examples/helloworld/pom.xml @@ -5,7 +5,7 @@ mini-examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/mini-examples/pom.xml b/examples/mini-examples/pom.xml index 9ac9f9a6c..dcc348b17 100644 --- a/examples/mini-examples/pom.xml +++ b/examples/mini-examples/pom.xml @@ -5,7 +5,7 @@ examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 pom diff --git a/examples/mini-examples/synchronizefx-example/pom.xml b/examples/mini-examples/synchronizefx-example/pom.xml index d0eb77356..93c6388f0 100644 --- a/examples/mini-examples/synchronizefx-example/pom.xml +++ b/examples/mini-examples/synchronizefx-example/pom.xml @@ -5,7 +5,7 @@ mini-examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/mini-examples/welcome-example/pom.xml b/examples/mini-examples/welcome-example/pom.xml index 18798b15f..687108930 100644 --- a/examples/mini-examples/welcome-example/pom.xml +++ b/examples/mini-examples/welcome-example/pom.xml @@ -5,7 +5,7 @@ mini-examples de.saxsys.mvvmfx - 1.4.0 + 1.4.1 4.0.0 diff --git a/examples/pom.xml b/examples/pom.xml index 76bb087d6..0566f8cb8 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -5,7 +5,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 de.saxsys.mvvmfx diff --git a/examples/todomvc-example/pom.xml b/examples/todomvc-example/pom.xml index e2528ee85..7a966b04b 100644 --- a/examples/todomvc-example/pom.xml +++ b/examples/todomvc-example/pom.xml @@ -5,7 +5,7 @@ de.saxsys.mvvmfx examples - 1.4.0 + 1.4.1 4.0.0 diff --git a/mvvmfx-archetype/pom.xml b/mvvmfx-archetype/pom.xml index b31255a3e..e75f5922c 100644 --- a/mvvmfx-archetype/pom.xml +++ b/mvvmfx-archetype/pom.xml @@ -5,7 +5,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 diff --git a/mvvmfx-archetype/src/main/resources/archetype-resources/pom.xml b/mvvmfx-archetype/src/main/resources/archetype-resources/pom.xml index 25f6e24a5..d2140a7c5 100644 --- a/mvvmfx-archetype/src/main/resources/archetype-resources/pom.xml +++ b/mvvmfx-archetype/src/main/resources/archetype-resources/pom.xml @@ -16,7 +16,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 pom import diff --git a/mvvmfx-cdi/pom.xml b/mvvmfx-cdi/pom.xml index 51648581f..694343b49 100644 --- a/mvvmfx-cdi/pom.xml +++ b/mvvmfx-cdi/pom.xml @@ -20,7 +20,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 mvvmfx-cdi diff --git a/mvvmfx-guice/pom.xml b/mvvmfx-guice/pom.xml index 0ac2bb071..b81a38ba1 100644 --- a/mvvmfx-guice/pom.xml +++ b/mvvmfx-guice/pom.xml @@ -20,7 +20,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 mvvmfx-guice diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.java new file mode 100644 index 000000000..298668a7b --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.java @@ -0,0 +1,18 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; + +public class InterceptedView implements FxmlView { + public static boolean vmWasInjected = false; + public static boolean wasInitCalled = false; + + @InjectViewModel + private InterceptedViewModel viewModel; + + public void initialize() { + wasInitCalled = true; + vmWasInjected = viewModel != null; + } + +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedViewModel.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedViewModel.java new file mode 100644 index 000000000..d13696a0c --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedViewModel.java @@ -0,0 +1,9 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + +import de.saxsys.mvvmfx.ViewModel; + +public class InterceptedViewModel implements ViewModel { + + + +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorModule.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorModule.java new file mode 100644 index 000000000..5ba9854a0 --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorModule.java @@ -0,0 +1,12 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + +import com.google.inject.Binder; +import com.google.inject.Module; +import com.google.inject.matcher.Matchers; + +public class InterceptorModule implements Module { + @Override + public void configure(Binder binder) { + binder.bindInterceptor(Matchers.subclassesOf(InterceptedView.class), Matchers.any(), new TestInterceptor()); + } +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTest.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTest.java new file mode 100644 index 000000000..af1e31f23 --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTest.java @@ -0,0 +1,35 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + + +import org.junit.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Test case to check AOP features (Interceptors) of Guice with mvvmFX. + */ +public class InterceptorTest { + + + @Test +// @Ignore("until fixed") + public void test() { + + InterceptedView.vmWasInjected = false; + InterceptedView.wasInitCalled = false; + TestInterceptor.wasIntercepted = false; + + + InterceptorTestApp.main(); + + final InterceptedView codeBehind = InterceptorTestApp.viewTuple.getCodeBehind(); + + + assertThat(TestInterceptor.wasIntercepted).isTrue(); + + + assertThat(InterceptedView.wasInitCalled).isTrue(); + assertThat(InterceptedView.vmWasInjected).isTrue(); + } + +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTestApp.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTestApp.java new file mode 100644 index 000000000..de55127a6 --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/InterceptorTestApp.java @@ -0,0 +1,36 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + +import com.google.inject.Module; +import de.saxsys.mvvmfx.FluentViewLoader; +import de.saxsys.mvvmfx.ViewTuple; +import de.saxsys.mvvmfx.guice.MvvmfxGuiceApplication; +import javafx.application.Platform; +import javafx.stage.Stage; + +import java.util.List; + +public class InterceptorTestApp extends MvvmfxGuiceApplication { + + public static ViewTuple viewTuple; + + + public static void main(String... args) { + launch(args); + } + + + @Override + public void startMvvmfx(Stage stage) throws Exception { + + viewTuple = FluentViewLoader.fxmlView(InterceptedView.class).load(); + + // we can't shutdown the application in the test case so we need to do it here. + Platform.exit(); + } + + + @Override + public void initGuiceModules(List modules) throws Exception { + modules.add(new InterceptorModule()); + } +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/TestInterceptor.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/TestInterceptor.java new file mode 100644 index 000000000..3a2958e2e --- /dev/null +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/interceptiontest/TestInterceptor.java @@ -0,0 +1,15 @@ +package de.saxsys.mvvmfx.guice.interceptiontest; + +import org.aopalliance.intercept.MethodInterceptor; +import org.aopalliance.intercept.MethodInvocation; + +public class TestInterceptor implements MethodInterceptor { + public static boolean wasIntercepted = false; + + @Override + public Object invoke(MethodInvocation methodInvocation) throws Throwable { + wasIntercepted = true; + + return methodInvocation.proceed(); + } +} diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/IntegrationTest.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/IntegrationTest.java index f57ef984c..ef42fb349 100644 --- a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/IntegrationTest.java +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/IntegrationTest.java @@ -1,11 +1,10 @@ package de.saxsys.mvvmfx.guice.it; -import static org.assertj.core.api.Assertions.*; - +import de.saxsys.mvvmfx.guice.internal.GuiceInjector; import org.junit.Before; import org.junit.Test; -import de.saxsys.mvvmfx.guice.internal.GuiceInjector; +import static org.assertj.core.api.Assertions.assertThat; public class IntegrationTest { @@ -15,6 +14,7 @@ public void setup() { MyApp.wasStopCalled = false; MyModule.works = false; MyViewModel.works = false; + MyView.wasInitCalled = false; } @Test @@ -48,6 +48,8 @@ public void testInjectionOfPrimaryStage() { assertThat(MyModule.works).isTrue(); assertThat(MyViewModel.works).isTrue(); + + assertThat(MyView.wasInitCalled).isTrue(); } diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyView.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyView.java index 0b8270219..1b1d176aa 100644 --- a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyView.java +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyView.java @@ -1,5 +1,6 @@ package de.saxsys.mvvmfx.guice.it; +import de.saxsys.mvvmfx.FxmlView; import de.saxsys.mvvmfx.utils.notifications.NotificationCenter; import javafx.application.Application; import javafx.application.HostServices; @@ -7,8 +8,6 @@ import javax.inject.Inject; -import de.saxsys.mvvmfx.FxmlView; - public class MyView implements FxmlView { @Inject Stage primaryStage; @@ -21,5 +20,11 @@ public class MyView implements FxmlView { @Inject HostServices hostServices; - + + public static boolean wasInitCalled = false; + + + public void initialize() { + wasInitCalled = true; + } } diff --git a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyViewModel.java b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyViewModel.java index ad4aebdbd..e5581bad4 100644 --- a/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyViewModel.java +++ b/mvvmfx-guice/src/test/java/de/saxsys/mvvmfx/guice/it/MyViewModel.java @@ -18,6 +18,4 @@ public MyViewModel(MyService service) { assertThat(service).isNotNull(); works = true; } - - } diff --git a/mvvmfx-guice/src/test/resources/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.fxml b/mvvmfx-guice/src/test/resources/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.fxml new file mode 100644 index 000000000..403adccbc --- /dev/null +++ b/mvvmfx-guice/src/test/resources/de/saxsys/mvvmfx/guice/interceptiontest/InterceptedView.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/mvvmfx-testing-utils/pom.xml b/mvvmfx-testing-utils/pom.xml index 54f2076af..684867d15 100644 --- a/mvvmfx-testing-utils/pom.xml +++ b/mvvmfx-testing-utils/pom.xml @@ -5,7 +5,7 @@ mvvmfx-parent de.saxsys - 1.4.0 + 1.4.1 4.0.0 diff --git a/mvvmfx-utils/pom.xml b/mvvmfx-utils/pom.xml index 31fe3744b..697fe9026 100644 --- a/mvvmfx-utils/pom.xml +++ b/mvvmfx-utils/pom.xml @@ -5,7 +5,7 @@ mvvmfx-parent de.saxsys - 1.4.0 + 1.4.1 4.0.0 diff --git a/mvvmfx/pom.xml b/mvvmfx/pom.xml index 2199f817b..6e2b1512d 100644 --- a/mvvmfx/pom.xml +++ b/mvvmfx/pom.xml @@ -20,7 +20,7 @@ de.saxsys mvvmfx-parent - 1.4.0 + 1.4.1 mvvmfx diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/FxmlViewLoader.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/FxmlViewLoader.java index e5e6aa19e..37cd80f46 100644 --- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/FxmlViewLoader.java +++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/FxmlViewLoader.java @@ -15,20 +15,18 @@ ******************************************************************************/ package de.saxsys.mvvmfx.internal.viewloader; -import java.io.IOException; -import java.net.URL; -import java.util.Optional; -import java.util.ResourceBundle; - +import de.saxsys.mvvmfx.ViewModel; +import de.saxsys.mvvmfx.ViewTuple; import javafx.fxml.FXMLLoader; import javafx.scene.Parent; import javafx.util.Callback; - import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import de.saxsys.mvvmfx.ViewModel; -import de.saxsys.mvvmfx.ViewTuple; +import java.io.IOException; +import java.net.URL; +import java.util.Optional; +import java.util.ResourceBundle; /** * This viewLoader is used to load views that are implementing {@link de.saxsys.mvvmfx.FxmlView}. @@ -52,12 +50,12 @@ public class FxmlViewLoader { * the root object that is passed to the {@link javafx.fxml.FXMLLoader} * @param viewModel * the viewModel instance that is used when loading the viewTuple. - * + * * @param * the generic type of the view. * @param * the generic type of the viewModel. - * + * * @return the loaded ViewTuple. */ public , ViewModelType extends ViewModel> ViewTuple loadFxmlViewTuple( @@ -102,7 +100,7 @@ private String createFxmlPath(Class viewType) { * * @param resource * the string path to the fxml file that is loaded. - * + * * @param resourceBundle * the resourceBundle that is passed to the {@link javafx.fxml.FXMLLoader}. * @param codeBehind @@ -111,12 +109,12 @@ private String createFxmlPath(Class viewType) { * the root object that is passed to the {@link javafx.fxml.FXMLLoader} * @param viewModel * the viewModel instance that is used when loading the viewTuple. - * + * * @param * the generic type of the view. * @param * the generic type of the viewModel. - * + * * @return the loaded ViewTuple. */ public , ViewModelType extends ViewModel> ViewTuple loadFxmlViewTuple( @@ -165,8 +163,8 @@ public , ViewModelType extends Vi private FXMLLoader createFxmlLoader(String resource, ResourceBundle resourceBundle, View codeBehind, Object root, ViewModel viewModel) - throws IOException { - + throws IOException { + // Load FXML file final URL location = FxmlViewLoader.class.getResource(resource); if (location == null) { @@ -207,7 +205,7 @@ private FXMLLoader createFxmlLoader(String resource, ResourceBundle resourceBund * a view. */ private static class DefaultControllerFactory implements Callback, Object> { - private ResourceBundle resourceBundle; + private final ResourceBundle resourceBundle; public DefaultControllerFactory(ResourceBundle resourceBundle) { this.resourceBundle = resourceBundle; @@ -276,9 +274,9 @@ private static class ControllerFactoryForCustomViewModel implements Callback * the generic type of the view. @@ -69,7 +69,7 @@ public class JavaViewLoader { */ public , ViewModelType extends ViewModel> ViewTuple loadJavaViewTuple( Class - viewType, ResourceBundle resourceBundle, ViewModelType viewModel) { + viewType, ResourceBundle resourceBundle, final ViewModelType existingViewModel) { DependencyInjector injectionFacade = DependencyInjector.getInstance(); final ViewType view = injectionFacade.getInstanceOf(viewType); @@ -78,21 +78,41 @@ public , ViewModelType extends Vi throw new IllegalArgumentException("Can not load java view! The view class has to extend from " + Parent.class.getName() + " or one of it's subclasses"); } - - if (viewModel == null) { - viewModel = ViewLoaderReflectionUtils.createViewModel(view); - } + + + ViewModelType viewModel = null; + + + // when no viewmodel was provided by the user... + if (existingViewModel == null) { + // ... we create a new one (if possible) + viewModel = ViewLoaderReflectionUtils.createViewModel(view); + } else { + viewModel = existingViewModel; + } ResourceBundleInjector.injectResourceBundle(view, resourceBundle); - - if (viewModel != null) { - ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle); - ViewLoaderReflectionUtils.initializeViewModel(viewModel); - ViewLoaderReflectionUtils.injectViewModel(view, viewModel); - } - - - if (view instanceof Initializable) { + + + // if no ViewModel is available... + if (viewModel == null) { + // we need to check if the user is trying to inject a viewModel. + + final List viewModelFields = ViewLoaderReflectionUtils.getViewModelFields(viewType); + + if(!viewModelFields.isEmpty()) { + throw new RuntimeException("The given view of type <" + view.getClass() + "> has no generic viewModel type declared but tries to inject a viewModel."); + } + + + } else { + ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle); + ViewLoaderReflectionUtils.initializeViewModel(viewModel); + ViewLoaderReflectionUtils.injectViewModel(view, viewModel); + } + + + if (view instanceof Initializable) { Initializable initializable = (Initializable) view; initializable.initialize(null, resourceBundle); } else { diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ReflectionUtils.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ReflectionUtils.java index 27f3af019..836f6fe0c 100644 --- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ReflectionUtils.java +++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ReflectionUtils.java @@ -4,6 +4,7 @@ import java.lang.reflect.Field; import java.security.AccessController; import java.security.PrivilegedAction; +import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.Callable; @@ -35,12 +36,43 @@ public static interface SideEffect { * @return a List of Fields that are annotated with the given annotation. */ public static List getFieldsWithAnnotation(Object target, Class annotationType) { - return Arrays.stream(target.getClass().getDeclaredFields()) + return ReflectionUtils.getFieldsFromClassHierarchy(target.getClass()) + .stream() .filter(field -> field.isAnnotationPresent(annotationType)) .collect(Collectors.toList()); } + /** + * Returns all fields of the given type and all parent types (except Object). + *
+ * The difference to {@link Class#getFields()} is that getFields only returns public fields while this method will + * return all fields whatever the access modifier is. + *
+ * + * The difference to {@link Class#getDeclaredFields()} is that getDeclaredFields returns all fields (with all + * modifiers) from the given class but not from super classes. This method instead will return all fields of all + * modifiers from all super classes up in the class hierarchy, except from Object itself. + * + * @param type the type whose fields will be searched. + * @return a list of field instances. + */ + public static List getFieldsFromClassHierarchy(Class type) { + + final List classFields = new ArrayList<>(); + classFields.addAll(Arrays.asList(type.getDeclaredFields())); + final Class parentClass = type.getSuperclass(); + + if (parentClass != null && !(parentClass.equals(Object.class))) { + List parentClassFields = getFieldsFromClassHierarchy(parentClass); + classFields.addAll(parentClassFields); + } + + return classFields; + } + + + /** * Helper method to execute a callback on a given field. This method encapsulates the error handling logic and the * handling of accessibility of the field. @@ -53,9 +85,9 @@ public static List getFieldsWithAnnotation(Object target, Class getViewModelField(Class viewType, Class viewModelType) { List allViewModelFields = getViewModelFields(viewType); - if(allViewModelFields.isEmpty()) { + if (allViewModelFields.isEmpty()) { return Optional.empty(); } @@ -47,13 +46,20 @@ public static Optional getViewModelField(Class viewType, Field field = allViewModelFields.get(0); - if(! ViewModel.class.isAssignableFrom(field.getType())) { - throw new RuntimeException("The View <" + viewType + "> has a field annotated with @InjectViewModel but the type of the field doesn't implement the 'ViewModel' interface!"); + if (!ViewModel.class.isAssignableFrom(field.getType())) { + throw new RuntimeException( + "The View <" + + viewType + + "> has a field annotated with @InjectViewModel but the type of the field doesn't implement the 'ViewModel' interface!"); } - if(! field.getType().isAssignableFrom(viewModelType)) { - throw new RuntimeException("The View <" + viewType + "> has a field annotated with @InjectViewModel but the type of the field doesn't match the generic ViewModel type of the View class. " - + "The declared generic type is <" + viewModelType + "> but the actual type of the field is <" + field.getType() + ">."); + if (!field.getType().isAssignableFrom(viewModelType)) { + throw new RuntimeException( + "The View <" + + viewType + + "> has a field annotated with @InjectViewModel but the type of the field doesn't match the generic ViewModel type of the View class. " + + "The declared generic type is <" + viewModelType + + "> but the actual type of the field is <" + field.getType() + ">."); } return Optional.of(field); @@ -61,16 +67,20 @@ public static Optional getViewModelField(Class viewType, /** - * Returns a list of all {@link Field}s of ViewModels for a given view type that are annotated with {@link InjectViewModel}. + * Returns a list of all {@link Field}s of ViewModels for a given view type that are annotated with + * {@link InjectViewModel}. * - * @param viewType the type of the view. + * @param viewType + * the type of the view. * @return a list of fields. */ - private static List getViewModelFields(Class viewType) { - return Arrays.stream(viewType.getDeclaredFields()) + public static List getViewModelFields(Class viewType) { + return ReflectionUtils.getFieldsFromClassHierarchy(viewType).stream() .filter(field -> field.isAnnotationPresent(InjectViewModel.class)) .collect(Collectors.toList()); } + + /** @@ -139,8 +149,10 @@ public static void injectViewModel(final View view, ViewModel viewModel) { * the generic type of the ViewModel. * @return an Optional containing the ViewModel if it was created or already existing. Otherwise the Optional is * empty. - * - * @throws RuntimeException if there is a ViewModel field in the View with the {@link InjectViewModel} annotation whose type doesn't match the generic ViewModel type from the View class. + * + * @throws RuntimeException + * if there is a ViewModel field in the View with the {@link InjectViewModel} annotation whose type + * doesn't match the generic ViewModel type from the View class. */ @SuppressWarnings("unchecked") public static , VM extends ViewModel> Optional createAndInjectViewModel( @@ -148,6 +160,14 @@ public static , VM extends ViewModel> Optional final Class viewModelType = TypeResolver.resolveRawArgument(View.class, view.getClass()); if (viewModelType == ViewModel.class) { + // if no viewModel can be created, we have to check if the user has tried to inject a ViewModel + final List viewModelFields = ViewLoaderReflectionUtils.getViewModelFields(view.getClass()); + + if(!viewModelFields.isEmpty()) { + throw new RuntimeException("The given view of type <" + view.getClass() + "> has no generic viewModel type declared but tries to inject a viewModel."); + } + + return Optional.empty(); } if (viewModelType == TypeResolver.Unknown.class) { diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/commands/DelegateCommand.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/commands/DelegateCommand.java index ac476b06e..d369e1a05 100644 --- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/commands/DelegateCommand.java +++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/commands/DelegateCommand.java @@ -126,7 +126,7 @@ public void execute() { try { actionSupplier.get().action(); } catch (Exception e) { - e.printStackTrace(); + throw new RuntimeException(e); } } } diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapper.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapper.java index 13561f253..339b62b91 100644 --- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapper.java +++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapper.java @@ -1,5 +1,17 @@ package de.saxsys.mvvmfx.utils.mapping; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.function.Supplier; + import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanGetter; import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanPropertyAccessor; import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.BooleanSetter; @@ -25,16 +37,29 @@ import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringPropertyAccessor; import de.saxsys.mvvmfx.utils.mapping.accessorfunctions.StringSetter; import eu.lestard.doc.Beta; -import javafx.beans.property.*; +import javafx.beans.property.BooleanProperty; +import javafx.beans.property.DoubleProperty; +import javafx.beans.property.FloatProperty; +import javafx.beans.property.IntegerProperty; +import javafx.beans.property.ListProperty; +import javafx.beans.property.LongProperty; +import javafx.beans.property.ObjectProperty; +import javafx.beans.property.Property; +import javafx.beans.property.ReadOnlyBooleanProperty; +import javafx.beans.property.ReadOnlyBooleanWrapper; +import javafx.beans.property.SimpleBooleanProperty; +import javafx.beans.property.SimpleDoubleProperty; +import javafx.beans.property.SimpleFloatProperty; +import javafx.beans.property.SimpleIntegerProperty; +import javafx.beans.property.SimpleListProperty; +import javafx.beans.property.SimpleLongProperty; +import javafx.beans.property.SimpleObjectProperty; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; import javafx.collections.FXCollections; import javafx.collections.ListChangeListener; import javafx.collections.ObservableList; -import java.util.*; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.Supplier; - /** * A helper class that can be used to simplify the mapping between the ViewModel and the Model for use cases where a @@ -204,24 +229,26 @@ * * * - * - * - * * @param * the type of the model class. */ @Beta public class ModelWrapper { - - private ReadOnlyBooleanWrapper dirtyFlag = new ReadOnlyBooleanWrapper(); - private ReadOnlyBooleanWrapper diffFlag = new ReadOnlyBooleanWrapper(); - - + + private final ReadOnlyBooleanWrapper dirtyFlag = new ReadOnlyBooleanWrapper(); + private final ReadOnlyBooleanWrapper diffFlag = new ReadOnlyBooleanWrapper(); + + /** * This interface defines the operations that are possible for each field of a wrapped class. * * @param + * target type. The base type of the returned property, f.e. {@link String}. * @param + * model type. The type of the Model class, that is wrapped by this ModelWrapper instance. + * @param + * return type. The type of the Property that is returned via {@link #getProperty()}, f.e. + * {@link StringProperty} or {@link Property}. */ private interface PropertyField> { void commit(M wrappedObject); @@ -231,8 +258,18 @@ private interface PropertyField> { void resetToDefault(); R getProperty(); - - boolean isDifferent(M wrappedObject); + + /** + * Determines if the value in the model object and the property field are different or not. + * + * This method is used to implement the {@link #differentProperty()} flag. + * + * @param wrappedObject + * the wrapped model object + * @return false if both the wrapped model object and the property field have the same value, + * otherwise true + */ + boolean isDifferent(M wrappedObject); } /** @@ -252,12 +289,13 @@ public FxPropertyField(Function> accessor, Supplier> } @SuppressWarnings("unchecked") - public FxPropertyField(Function> accessor, T defaultValue, Supplier> propertySupplier) { + public FxPropertyField(Function> accessor, T defaultValue, + Supplier> propertySupplier) { this.accessor = accessor; this.defaultValue = defaultValue; this.targetProperty = (R) propertySupplier.get(); - - this.targetProperty.addListener((observable, oldValue, newValue) -> propertyWasChanged()); + + this.targetProperty.addListener((observable, oldValue, newValue) -> propertyWasChanged()); } @Override @@ -279,15 +317,15 @@ public void resetToDefault() { public R getProperty() { return targetProperty; } - - @Override - public boolean isDifferent(M wrappedObject) { - final T modelValue = accessor.apply(wrappedObject).getValue(); - final T wrapperValue = targetProperty.getValue(); - - return !Objects.equals(modelValue, wrapperValue); - } - } + + @Override + public boolean isDifferent(M wrappedObject) { + final T modelValue = accessor.apply(wrappedObject).getValue(); + final T wrapperValue = targetProperty.getValue(); + + return !Objects.equals(modelValue, wrapperValue); + } + } /** * An implementation of {@link PropertyField} that is used when the fields of the model class are not JavaFX @@ -314,8 +352,8 @@ public BeanPropertyField(Function getter, this.getter = getter; this.setter = setter; this.targetProperty = propertySupplier.get(); - - this.targetProperty.addListener((observable, oldValue, newValue) -> propertyWasChanged()); + + this.targetProperty.addListener((observable, oldValue, newValue) -> propertyWasChanged()); } @Override @@ -337,19 +375,19 @@ public void resetToDefault() { public R getProperty() { return targetProperty; } - - @Override - public boolean isDifferent(M wrappedObject) { - final T modelValue = getter.apply(wrappedObject); - final T wrapperValue = targetProperty.getValue(); - - return !Objects.equals(modelValue, wrapperValue); - } - } + + @Override + public boolean isDifferent(M wrappedObject) { + final T modelValue = getter.apply(wrappedObject); + final T wrapperValue = targetProperty.getValue(); + + return !Objects.equals(modelValue, wrapperValue); + } + } /** * An implementation of {@link PropertyField} that is used when the field of the model class is a {@link List} and - * and is a JavaFX {@link ListProperty} too. + * will be mapped to a JavaFX {@link ListProperty}. * * @param * @param @@ -357,55 +395,55 @@ public boolean isDifferent(M wrappedObject) { */ private class FxListPropertyField, R extends Property> implements PropertyField { - + private final List defaultValue; private final ListPropertyAccessor accessor; private final ListProperty targetProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - + public FxListPropertyField(ListPropertyAccessor accessor) { this(accessor, Collections.emptyList()); } - + public FxListPropertyField(ListPropertyAccessor accessor, List defaultValue) { this.accessor = accessor; this.defaultValue = defaultValue; - - this.targetProperty.addListener((ListChangeListener) change -> propertyWasChanged()); + + this.targetProperty.addListener((ListChangeListener) change -> ModelWrapper.this.propertyWasChanged()); } - + @Override public void commit(M wrappedObject) { accessor.apply(wrappedObject).setAll(targetProperty.getValue()); } - + @Override public void reload(M wrappedObject) { targetProperty.setAll(accessor.apply(wrappedObject).getValue()); } - + @Override public void resetToDefault() { targetProperty.setAll(defaultValue); } - + @Override public R getProperty() { return (R) targetProperty; } - + @Override public boolean isDifferent(M wrappedObject) { final List modelValue = accessor.apply(wrappedObject).getValue(); final List wrapperValue = targetProperty; - - return !(modelValue.containsAll(wrapperValue) && wrapperValue.containsAll(modelValue)); + + return !Objects.equals(modelValue, wrapperValue); } } - + /** * An implementation of {@link PropertyField} that is used when the field of the model class is a {@link List} and - * is not a JavaFX ListProperty but is following the old Java-Beans standard, i.e. there is getter and - * setter method for the field. + * is not a JavaFX ListProperty but is following the old Java-Beans standard, i.e. there is getter and setter + * method for the field. * * @param * @param @@ -413,56 +451,56 @@ public boolean isDifferent(M wrappedObject) { */ private class BeanListPropertyField, R extends Property> implements PropertyField { - + private final ListGetter getter; private final ListSetter setter; - + private final List defaultValue; private final ListProperty targetProperty = new SimpleListProperty<>(FXCollections.observableArrayList()); - + public BeanListPropertyField(ListGetter getter, ListSetter setter) { this(getter, setter, Collections.emptyList()); } - + public BeanListPropertyField(ListGetter getter, ListSetter setter, List defaultValue) { this.defaultValue = defaultValue; this.getter = getter; this.setter = setter; - - this.targetProperty.addListener((ListChangeListener) change -> propertyWasChanged()); + + this.targetProperty.addListener((ListChangeListener) change -> propertyWasChanged()); } - + @Override public void commit(M wrappedObject) { setter.accept(wrappedObject, targetProperty.getValue()); } - + @Override public void reload(M wrappedObject) { targetProperty.setAll(getter.apply(wrappedObject)); } - + @Override public void resetToDefault() { targetProperty.setAll(defaultValue); } - + @Override public R getProperty() { return (R) targetProperty; } - + @Override public boolean isDifferent(M wrappedObject) { final List modelValue = getter.apply(wrappedObject); final List wrapperValue = targetProperty; - - return !(modelValue.containsAll(wrapperValue) && wrapperValue.containsAll(modelValue)); + + return !Objects.equals(modelValue, wrapperValue); } } - private Set> fields = new HashSet<>(); - private Map> identifiedFields = new HashMap<>(); + private final Set> fields = new HashSet<>(); + private final Map> identifiedFields = new HashMap<>(); private M model; @@ -511,8 +549,8 @@ public M get() { */ public void reset() { fields.forEach(field -> field.resetToDefault()); - - calculateDifferenceFlag(); + + calculateDifferenceFlag(); } /** @@ -526,10 +564,10 @@ public void reset() { public void commit() { if (model != null) { fields.forEach(field -> field.commit(model)); - - dirtyFlag.set(false); - - calculateDifferenceFlag(); + + dirtyFlag.set(false); + + calculateDifferenceFlag(); } } @@ -544,28 +582,28 @@ public void commit() { public void reload() { if (model != null) { fields.forEach(field -> field.reload(model)); - - dirtyFlag.set(false); - calculateDifferenceFlag(); + + dirtyFlag.set(false); + calculateDifferenceFlag(); + } + } + + + + private void propertyWasChanged() { + dirtyFlag.set(true); + calculateDifferenceFlag(); + } + + private void calculateDifferenceFlag() { + if (model != null) { + final Optional> optional = fields.stream() + .filter(field -> field.isDifferent(model)) + .findAny(); + + diffFlag.set(optional.isPresent()); } } - - - - private void propertyWasChanged(){ - dirtyFlag.set(true); - calculateDifferenceFlag(); - } - - private void calculateDifferenceFlag(){ - if(model != null) { - final Optional> optional = fields.stream() - .filter(field -> field.isDifferent(model)) - .findAny(); - - diffFlag.set(optional.isPresent()); - } - } @@ -599,7 +637,7 @@ private void calculateDifferenceFlag(){ * @param setter * a function that sets the given value to the given model element. Typically you will use a method * reference to the setter method of the model element. - * + * * @return The wrapped property instance. */ public StringProperty field(StringGetter getter, StringSetter setter) { @@ -620,7 +658,7 @@ public StringProperty field(StringGetter getter, StringSetter setter) { * reference to the setter method of the model element. * @param defaultValue * the default value that is used when {@link #reset()} is invoked. - * + * * @return The wrapped property instance. */ public StringProperty field(StringGetter getter, StringSetter setter, String defaultValue) { @@ -648,7 +686,7 @@ public StringProperty field(StringGetter getter, StringSetter setter, Stri * @param accessor * a function that returns the property for a given model instance. Typically you will use a method * reference to the javafx-property accessor method. - * + * * @return The wrapped property instance. */ public StringProperty field(StringPropertyAccessor accessor) { @@ -694,7 +732,8 @@ public StringProperty field(String identifier, StringGetter getter, StringSet return addIdentified(identifier, new BeanPropertyField<>(getter, setter, SimpleStringProperty::new)); } - public StringProperty field(String identifier, StringGetter getter, StringSetter setter, String defaultValue) { + public StringProperty field(String identifier, StringGetter getter, StringSetter setter, + String defaultValue) { return addIdentified(identifier, new BeanPropertyField<>(getter, setter, defaultValue, SimpleStringProperty::new)); } @@ -707,7 +746,7 @@ public StringProperty field(String identifier, StringGetter getter, StringSet * * @param identifier * an identifier for the field. - * + * * @param accessor * a function that returns the property for a given model instance. Typically you will use a method * reference to the javafx-property accessor method. @@ -764,14 +803,18 @@ public BooleanProperty field(String identifier, BooleanPropertyAccessor acces public DoubleProperty field(DoubleGetter getter, DoubleSetter setter) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), - SimpleDoubleProperty::new)); + final ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), + SimpleDoubleProperty::new); + return add(beanPropertyField); } public DoubleProperty field(DoubleGetter getter, DoubleSetter setter, double defaultValue) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), + final ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), defaultValue, - SimpleDoubleProperty::new)); + SimpleDoubleProperty::new); + return add(beanPropertyField); } public DoubleProperty field(DoublePropertyAccessor accessor) { @@ -783,16 +826,20 @@ public DoubleProperty field(DoublePropertyAccessor accessor, double defaultVa } public DoubleProperty field(String identifier, DoubleGetter getter, DoubleSetter setter) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), - SimpleDoubleProperty::new)); + final ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), + SimpleDoubleProperty::new); + + return addIdentified(identifier, beanPropertyField); } - public DoubleProperty field(String identifier, DoubleGetter getter, DoubleSetter setter, double defaultValue) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), - defaultValue, - SimpleDoubleProperty::new)); + public DoubleProperty field(String identifier, DoubleGetter getter, DoubleSetter setter, + double defaultValue) { + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.doubleValue()), + defaultValue, + SimpleDoubleProperty::new); + return addIdentified(identifier, beanPropertyField); } public DoubleProperty field(String identifier, DoublePropertyAccessor accessor) { @@ -810,14 +857,17 @@ public DoubleProperty field(String identifier, DoublePropertyAccessor accesso /** Field type Float **/ public FloatProperty field(FloatGetter getter, FloatSetter setter) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.floatValue()), - SimpleFloatProperty::new)); + final ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.floatValue()), + SimpleFloatProperty::new); + return add(beanPropertyField); } public FloatProperty field(FloatGetter getter, FloatSetter setter, float defaultValue) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.floatValue()), - defaultValue, - SimpleFloatProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.floatValue()), defaultValue, + SimpleFloatProperty::new); + return add(beanPropertyField); } public FloatProperty field(FloatPropertyAccessor accessor) { @@ -829,16 +879,19 @@ public FloatProperty field(FloatPropertyAccessor accessor, float defaultValue } public FloatProperty field(String identifier, FloatGetter getter, FloatSetter setter) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.floatValue()), - SimpleFloatProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.floatValue()), + SimpleFloatProperty::new); + return addIdentified(identifier, beanPropertyField); } public FloatProperty field(String identifier, FloatGetter getter, FloatSetter setter, float defaultValue) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.floatValue()), - defaultValue, - SimpleFloatProperty::new)); + + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.floatValue()), + defaultValue, + SimpleFloatProperty::new); + return addIdentified(identifier, beanPropertyField); } public FloatProperty field(String identifier, FloatPropertyAccessor accessor) { @@ -846,7 +899,8 @@ public FloatProperty field(String identifier, FloatPropertyAccessor accessor) } public FloatProperty field(String identifier, FloatPropertyAccessor accessor, float defaultValue) { - return addIdentified(identifier, new FxPropertyField<>(accessor::apply, defaultValue, SimpleFloatProperty::new)); + return addIdentified(identifier, + new FxPropertyField<>(accessor::apply, defaultValue, SimpleFloatProperty::new)); } @@ -854,14 +908,18 @@ public FloatProperty field(String identifier, FloatPropertyAccessor accessor, public IntegerProperty field(IntGetter getter, IntSetter setter) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.intValue()), - SimpleIntegerProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.intValue()), + SimpleIntegerProperty::new); + return add(beanPropertyField); } public IntegerProperty field(IntGetter getter, IntSetter setter, int defaultValue) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.intValue()), + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.intValue()), defaultValue, - SimpleIntegerProperty::new)); + SimpleIntegerProperty::new); + return add(beanPropertyField); } @@ -874,16 +932,18 @@ public IntegerProperty field(IntPropertyAccessor accessor, int defaultValue) } public IntegerProperty field(String identifier, IntGetter getter, IntSetter setter) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.intValue()), - SimpleIntegerProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.intValue()), + SimpleIntegerProperty::new); + return addIdentified(identifier, beanPropertyField); } public IntegerProperty field(String identifier, IntGetter getter, IntSetter setter, int defaultValue) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.intValue()), - defaultValue, - SimpleIntegerProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.intValue()), + defaultValue, + SimpleIntegerProperty::new); + return addIdentified(identifier, beanPropertyField); } @@ -901,14 +961,18 @@ public IntegerProperty field(String identifier, IntPropertyAccessor accessor, /** Field type Long **/ public LongProperty field(LongGetter getter, LongSetter setter) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.longValue()), - SimpleLongProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.longValue()), + SimpleLongProperty::new); + return add(beanPropertyField); } public LongProperty field(LongGetter getter, LongSetter setter, long defaultValue) { - return add(new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.longValue()), + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.longValue()), defaultValue, - SimpleLongProperty::new)); + SimpleLongProperty::new); + return add(beanPropertyField); } public LongProperty field(LongPropertyAccessor accessor) { @@ -921,16 +985,19 @@ public LongProperty field(LongPropertyAccessor accessor, long defaultValue) { public LongProperty field(String identifier, LongGetter getter, LongSetter setter) { - return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.longValue()), - SimpleLongProperty::new)); + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.longValue()), + SimpleLongProperty::new); + return addIdentified(identifier, beanPropertyField); } public LongProperty field(String identifier, LongGetter getter, LongSetter setter, long defaultValue) { + ModelWrapper.BeanPropertyField beanPropertyField = new BeanPropertyField<>( + getter::apply, (m, number) -> setter.accept(m, number.longValue()), + defaultValue, + SimpleLongProperty::new); return addIdentified(identifier, - new BeanPropertyField<>(getter::apply, (m, number) -> setter.accept(m, number.longValue()), - defaultValue, - SimpleLongProperty::new)); + beanPropertyField); } public LongProperty field(String identifier, LongPropertyAccessor accessor) { @@ -981,44 +1048,44 @@ public ObjectProperty field(String identifier, ObjectPropertyAccessor(accessor::apply, defaultValue, SimpleObjectProperty::new)); } - - + + /** Field type list **/ - + public ListProperty field(ListGetter getter, ListSetter setter) { - return add(new BeanListPropertyField<>(getter::apply, (m, list) - -> setter.accept(m, FXCollections.observableArrayList(list)))); + return add(new BeanListPropertyField<>(getter::apply, + (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)))); } - + public ListProperty field(ListGetter getter, ListSetter setter, List defaultValue) { - return add(new BeanListPropertyField<>(getter::apply, (m, list) - -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue)); + return add(new BeanListPropertyField<>(getter::apply, + (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue)); } - + public ListProperty field(ListPropertyAccessor accessor) { return add(new FxListPropertyField<>(accessor::apply)); } - + public ListProperty field(ListPropertyAccessor accessor, List defaultValue) { return add(new FxListPropertyField<>(accessor::apply, defaultValue)); } - - + + public ListProperty field(String identifier, ListGetter getter, ListSetter setter) { - return addIdentified(identifier, new BeanListPropertyField<>(getter::apply, (m, list) - -> setter.accept(m, FXCollections.observableArrayList(list)))); + return addIdentified(identifier, new BeanListPropertyField<>(getter::apply, + (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)))); } - + public ListProperty field(String identifier, ListGetter getter, ListSetter setter, - List defaultValue) { - return addIdentified(identifier, new BeanListPropertyField<>(getter::apply, (m, list) - -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue)); + List defaultValue) { + return addIdentified(identifier, new BeanListPropertyField<>(getter::apply, + (m, list) -> setter.accept(m, FXCollections.observableArrayList(list)), defaultValue)); } - + public ListProperty field(String identifier, ListPropertyAccessor accessor) { return addIdentified(identifier, new FxListPropertyField<>(accessor::apply)); } - + public ListProperty field(String identifier, ListPropertyAccessor accessor, List defaultValue) { return addIdentified(identifier, new FxListPropertyField<>(accessor::apply, defaultValue)); } @@ -1041,63 +1108,61 @@ private > R addIdentified(String fieldName, PropertyFie return add(field); } } - - /** - * This boolean flag indicates whether there is a difference of the data between - * the wrapped model object and the properties provided by this wrapper. - *

- * Note the difference to {@link #dirtyProperty()}: - * This property will be true if the data of the wrapped model is different to - * the properties of this wrapper. If you change the data back to the initial state so that the data - * is equal again, this property will change back to false while the {@link #dirtyProperty()} - * will still be true. - * - * Simply speaking: This property indicates whether there is a difference in data between the model and the wrapper. - * The {@link #dirtyProperty()} indicates whether there was a change done. - * - * - * Note: Only those changes are observed that are done through the wrapped property fields of this wrapper. - * If you change the data of the model instance directly, this property won't turn to true. - * - * - * @return a reay-only property indicating a difference between model and wrapper. - */ + + /** + * This boolean flag indicates whether there is a difference of the data between the wrapped model object and the + * properties provided by this wrapper. + *

+ * Note the difference to {@link #dirtyProperty()}: This property will be true if the data of the + * wrapped model is different to the properties of this wrapper. If you change the data back to the initial state so + * that the data is equal again, this property will change back to false while the + * {@link #dirtyProperty()} will still be true. + * + * Simply speaking: This property indicates whether there is a difference in data between the model and the wrapper. + * The {@link #dirtyProperty()} indicates whether there was a change done. + * + * + * Note: Only those changes are observed that are done through the wrapped property fields of this wrapper. If you + * change the data of the model instance directly, this property won't turn to true. + * + * + * @return a reay-only property indicating a difference between model and wrapper. + */ public ReadOnlyBooleanProperty differentProperty() { return diffFlag.getReadOnlyProperty(); } - - /** - * See {@link #differentProperty()}. - */ + + /** + * See {@link #differentProperty()}. + */ public boolean isDifferent() { return diffFlag.get(); } - - /** - * This boolean flag indicates whether there was a change to at least one wrapped property. - *

- * Note the difference to {@link #differentProperty()}: - * This property will turn to true when the value of one of the wrapped properties is - * changed. It will only change back to false when either the {@link #commit()} or {@link #reload()} - * method is called. - * This property will stay true even if afterwards another change is done so that the - * data is equal again. In this case the {@link #differentProperty()} will switch back to false. - * - * Simply speaking: This property indicates whether there was a change done to the wrapped properties or not. - * The {@link #differentProperty()} indicates whether there is a difference in data at the moment. - * - * @return a read only boolean property indicating if there was a change done. - */ + + /** + * This boolean flag indicates whether there was a change to at least one wrapped property. + *

+ * Note the difference to {@link #differentProperty()}: This property will turn to true when the value + * of one of the wrapped properties is changed. It will only change back to false when either the + * {@link #commit()} or {@link #reload()} method is called. This property will stay true even if + * afterwards another change is done so that the data is equal again. In this case the {@link #differentProperty()} + * will switch back to false. + * + * Simply speaking: This property indicates whether there was a change done to the wrapped properties or not. The + * {@link #differentProperty()} indicates whether there is a difference in data at the moment. + * + * @return a read only boolean property indicating if there was a change done. + */ public ReadOnlyBooleanProperty dirtyProperty() { return dirtyFlag.getReadOnlyProperty(); } - - /** - * See {@link #dirtyProperty()}. - */ + + /** + * See {@link #dirtyProperty()}. + */ public boolean isDirty() { return dirtyFlag.get(); } - - + + } diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_FxmlView_Test.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_FxmlView_Test.java index 23af3ed48..6d17c6812 100644 --- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_FxmlView_Test.java +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_FxmlView_Test.java @@ -17,7 +17,6 @@ import de.saxsys.mvvmfx.FluentViewLoader; import de.saxsys.mvvmfx.InjectViewModel; -import de.saxsys.mvvmfx.MvvmFX; import de.saxsys.mvvmfx.ViewModel; import de.saxsys.mvvmfx.ViewTuple; import de.saxsys.mvvmfx.internal.viewloader.example.*; @@ -26,7 +25,6 @@ import javafx.fxml.LoadException; import javafx.scene.layout.VBox; import org.junit.Before; -import org.junit.Ignore; import org.junit.Test; import org.junit.runner.RunWith; @@ -35,8 +33,8 @@ import java.util.PropertyResourceBundle; import java.util.ResourceBundle; -import static org.assertj.core.api.Assertions.assertThat; import static de.saxsys.mvvmfx.internal.viewloader.ResourceBundleAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.fail; /** @@ -63,7 +61,7 @@ public void testLoadFxmlViewTuple() throws IOException { final ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlView.class) .resourceBundle(resourceBundle).load(); - + assertThat(viewTuple).isNotNull(); assertThat(viewTuple.getView()).isNotNull().isInstanceOf(VBox.class); @@ -91,7 +89,7 @@ public void test_initializeOfViewModel() { final ViewTuple viewTuple = FluentViewLoader .fxmlView(TestFxmlViewResourceBundle.class) .resourceBundle(resourceBundle).load(); - + // then assertThat(TestViewModelWithResourceBundle.wasInitialized).isTrue(); assertThat(TestViewModelWithResourceBundle.resourceBundleWasAvailableAtInitialize).isTrue(); @@ -111,7 +109,7 @@ public void testBugMultipleViewModelsCreated() { final ViewTuple viewTuple = FluentViewLoader .fxmlView(TestFxmlViewWithoutViewModelField.class).load(); - + assertThat(TestFxmlViewWithoutViewModelField.instanceCounter).isEqualTo(1); assertThat(TestViewModel.instanceCounter).isEqualTo(1); } @@ -128,7 +126,7 @@ public void testViewWithoutViewModelType() { final TestFxmlViewWithoutViewModelType codeBehind = (TestFxmlViewWithoutViewModelType) viewTuple .getCodeBehind(); - + assertThat(codeBehind.wasInitialized).isTrue(); } @@ -138,7 +136,7 @@ public void testViewWithFxRoot() { ViewTuple viewTuple = FluentViewLoader.fxmlView( TestFxmlViewFxRoot.class).codeBehind(root).root(root).load(); - + assertThat(viewTuple).isNotNull(); assertThat(viewTuple.getView()).isNotNull().isEqualTo(root); @@ -158,7 +156,7 @@ public void testUseExistingCodeBehind() { ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlViewWithMissingController.class).codeBehind(codeBehind).load(); - + assertThat(viewTuple).isNotNull(); assertThat(viewTuple.getCodeBehind()).isEqualTo(codeBehind); @@ -216,7 +214,7 @@ public void testUseExistingCodeBehindFailWhenControllerIsDefinedInFXML() { TestFxmlView codeBehind = new TestFxmlView(); // the fxml file for this class has a fx:controller defined. ViewTuple viewTuple = FluentViewLoader.fxmlView(TestFxmlView.class) .codeBehind(codeBehind).load(); - + fail("Expected a LoadException to be thrown"); } catch (Exception e) { assertThat(e).hasCauseInstanceOf(LoadException.class).hasMessageContaining( @@ -236,16 +234,16 @@ public void testLoadFailNoSuchFxmlFile() { */ @Test public void testAlreadyExistingViewModelShouldNotBeOverwritten() { - + TestFxmlViewWithMissingController codeBehind = new TestFxmlViewWithMissingController(); - + TestViewModel existingViewModel = new TestViewModel(); - + codeBehind.viewModel = existingViewModel; - + ViewTuple viewTuple = FluentViewLoader .fxmlView(TestFxmlViewWithMissingController.class).codeBehind(codeBehind).load(); - + assertThat(viewTuple.getCodeBehind()).isNotNull(); assertThat(viewTuple.getCodeBehind().viewModel).isEqualTo(existingViewModel); } @@ -275,16 +273,45 @@ public void testThrowExceptionWhenMoreThenOneViewModelIsDefinedInFxmlView() { } + @Test + public void testViewHasNoGenericViewModelTypeButInjectsViewModel() { + try { + FluentViewLoader.fxmlView(TestFxmlViewWithoutViewModelTypeButWithInjection.class).load(); + fail("Expected an Exception"); + } catch (Exception e) { + assertThat(ExceptionUtils.getRootCause(e)).isInstanceOf(RuntimeException.class) + .hasMessageContaining("but tries to inject a viewModel"); + } + } + + + /** + * like {@link #testViewHasNoGenericViewModelTypeButInjectsViewModel()} but this time the field in the View is of + * type {@link ViewModel}. + */ + @Test + public void testViewHasNoGenericViewModelTypeButInjectsViewModelOfIntefaceType() { + try { + FluentViewLoader.fxmlView(TestFxmlViewWithoutViewModelTypeButWithInjection2.class).load(); + fail("Expected an Exception"); + } catch (Exception e) { + assertThat(ExceptionUtils.getRootCause(e)).isInstanceOf(RuntimeException.class) + .hasMessageContaining("but tries to inject a viewModel"); + } + } + + @Test public void testThrowExceptionWhenWrongViewModelTypeIsInjected() { try { FluentViewLoader.fxmlView(TestFxmlViewWithWrongInjectedViewModel.class).load(); fail("Expected an Exception"); } catch (Exception e) { - assertThat(ExceptionUtils.getRootCause(e)).isInstanceOf(RuntimeException.class).hasMessageContaining("field doesn't match the generic ViewModel type "); + assertThat(ExceptionUtils.getRootCause(e)).isInstanceOf(RuntimeException.class) + .hasMessageContaining("field doesn't match the generic ViewModel type "); } } - + /** * The {@link InjectViewModel} annotation may only be used on fields whose Type are implementing {@link ViewModel}. */ @@ -334,7 +361,7 @@ public void testUseExistingViewModel() { ViewTuple viewTupleOne = FluentViewLoader.fxmlView(TestFxmlView.class) .viewModel(viewModel) .load(); - + assertThat(viewTupleOne).isNotNull(); assertThat(viewTupleOne.getCodeBehind().getViewModel()).isEqualTo(viewModel); @@ -344,7 +371,7 @@ public void testUseExistingViewModel() { ViewTuple viewTupleTwo = FluentViewLoader.fxmlView(TestFxmlView.class) .viewModel(viewModel) .load(); - + assertThat(viewTupleTwo).isNotNull(); assertThat(viewTupleTwo.getViewModel()).isEqualTo(viewModel); @@ -361,7 +388,7 @@ public void testViewModelIsAvailableInViewTupleEvenIfItIsntInjectedInTheView() { ViewTuple viewTuple = FluentViewLoader .fxmlView(TestFxmlViewWithoutViewModelField.class).load(); - + assertThat(viewTuple.getCodeBehind().wasInitialized).isTrue(); assertThat(viewTuple.getViewModel()).isNotNull(); @@ -398,7 +425,7 @@ public void testExistingViewModelWithoutInjectionInView() { final ViewTuple viewTuple = FluentViewLoader .fxmlView(TestFxmlViewWithoutViewModelField.class).viewModel(viewModel).load(); - + assertThat(viewTuple.getViewModel()).isEqualTo(viewModel); // we need to reset the DI diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_JavaView_Test.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_JavaView_Test.java index bcb23bf2c..d1fa2acf1 100644 --- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_JavaView_Test.java +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/FluentViewLoader_JavaView_Test.java @@ -1,34 +1,27 @@ package de.saxsys.mvvmfx.internal.viewloader; -import static de.saxsys.mvvmfx.internal.viewloader.ResourceBundleAssert.assertThat; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.fail; - -import java.io.StringReader; -import java.lang.reflect.Constructor; -import java.net.URL; -import java.util.PropertyResourceBundle; -import java.util.ResourceBundle; - -import de.saxsys.mvvmfx.FluentViewLoader; -import de.saxsys.mvvmfx.ViewModel; -import de.saxsys.mvvmfx.ViewTuple; +import de.saxsys.mvvmfx.*; +import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModel; import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModelA; import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModelB; import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModelWithResourceBundle; import de.saxsys.mvvmfx.testingutils.ExceptionUtils; import javafx.fxml.Initializable; import javafx.scene.layout.VBox; - import org.junit.After; import org.junit.Before; import org.junit.Test; -import de.saxsys.mvvmfx.InjectViewModel; -import de.saxsys.mvvmfx.JavaView; -import de.saxsys.mvvmfx.MvvmFX; -import de.saxsys.mvvmfx.internal.viewloader.example.TestViewModel; +import java.io.StringReader; +import java.lang.reflect.Constructor; +import java.net.URL; +import java.util.PropertyResourceBundle; +import java.util.ResourceBundle; + +import static de.saxsys.mvvmfx.internal.viewloader.ResourceBundleAssert.assertThat; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.fail; /** @@ -139,30 +132,24 @@ class TestView implements JavaView { } } - + /** * It is possible to define a view without specifying a viewModel type. */ @Test public void testViewWithoutViewModelType() { class TestView extends VBox implements JavaView { - @InjectViewModel - public TestViewModel viewModel; } - + ViewTuple viewTuple = FluentViewLoader.javaView(TestView.class).load(); - + assertThat(viewTuple).isNotNull(); assertThat(viewTuple.getViewModel()).isNull(); - + View codeBehind = viewTuple.getCodeBehind(); assertThat(codeBehind).isNotNull().isInstanceOf(TestView.class); - - TestView loadedView = (TestView) codeBehind; - - assertThat(loadedView.viewModel).isNull(); } - + /** * The ViewModel has to be injected before the explicit initialize method is called. */ @@ -262,6 +249,28 @@ class TestView extends VBox implements JavaView { } } + + /** + * A View without generic ViewModel type can't inject a ViewModel + */ + @Test + public void testViewWithoutViewModelTypeButViewModelInjection() { + class TestView extends VBox implements JavaView { + @InjectViewModel + public TestViewModel viewModel; + } + + try { + FluentViewLoader.javaView(TestView.class).load(); + fail("Expected an Exception"); + } catch (Exception e) { + Throwable rootCause = ExceptionUtils.getRootCause(e); + assertThat(rootCause).isInstanceOf(RuntimeException.class) + .hasMessageContaining("but tries to inject a viewModel"); + } + } + + /** * When the ViewModel isn't injected in the view it should still be available in the ViewTuple. */ @@ -273,7 +282,7 @@ class TestView extends VBox implements JavaView { ViewTuple viewTuple = FluentViewLoader .javaView(TestView.class).load(); - + assertThat(viewTuple.getViewModel()).isNotNull(); } @@ -289,7 +298,7 @@ class TestView extends VBox implements JavaView { ViewTuple viewTuple = FluentViewLoader.javaView(TestView.class).viewModel(viewModel) .load(); - + assertThat(viewTuple.getCodeBehind().viewModel).isEqualTo(viewModel); assertThat(viewTuple.getViewModel()).isEqualTo(viewModel); } @@ -405,7 +414,7 @@ class TestView extends VBox implements JavaView { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resources).hasSameContent(resourceBundle); } @@ -427,7 +436,7 @@ public void initialize() { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resourcesWasInjected).isTrue(); } @@ -445,7 +454,7 @@ public ResourceBundle getResources() { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.getResources()).isNull(); } @@ -461,7 +470,7 @@ class TestView extends VBox implements JavaView { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resources).isInstanceOf(ResourceBundle.class); ResourceBundle resourceBundle = (ResourceBundle)loadedView.resources; assertThat(resourceBundle).isNotNull().hasSameContent(resourceBundle); @@ -480,7 +489,7 @@ class TestView extends VBox implements JavaView { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resources).isNull(); } @@ -497,7 +506,7 @@ public void initialize(URL location, ResourceBundle resources) { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resourceBundle).hasSameContent(resourceBundle); } @@ -516,7 +525,7 @@ public void initialize(URL location, ResourceBundle resources) { TestView loadedView = FluentViewLoader.javaView(TestView.class).resourceBundle(resourceBundle).load() .getCodeBehind(); - + assertThat(loadedView.resources).isNull(); } diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.java new file mode 100644 index 000000000..673c8e5de --- /dev/null +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.java @@ -0,0 +1,11 @@ +package de.saxsys.mvvmfx.internal.viewloader.example; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; + +public class TestFxmlViewWithoutViewModelTypeButWithInjection implements FxmlView { + + @InjectViewModel + public TestViewModel viewModel; + +} diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.java new file mode 100644 index 000000000..db9ff434a --- /dev/null +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.java @@ -0,0 +1,12 @@ +package de.saxsys.mvvmfx.internal.viewloader.example; + +import de.saxsys.mvvmfx.FxmlView; +import de.saxsys.mvvmfx.InjectViewModel; +import de.saxsys.mvvmfx.ViewModel; + +public class TestFxmlViewWithoutViewModelTypeButWithInjection2 implements FxmlView { + + @InjectViewModel + public ViewModel viewModel; + +} diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java index 45cadbef1..a88f00182 100644 --- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/ModelWrapperTest.java @@ -372,9 +372,6 @@ public void testDifferentFlag() { assertThat(personWrapper.isDifferent()).isFalse(); - nicknames.remove("captain"); - assertThat(personWrapper.isDifferent()).isTrue(); - nicknames.remove("captain"); assertThat(personWrapper.isDifferent()).isTrue(); @@ -393,8 +390,19 @@ public void testDifferentFlag() { personWrapper.reload(); assertThat(personWrapper.isDifferent()).isFalse(); - nicknames.add("captain"); - assertThat(personWrapper.isDifferent()).isFalse(); + nicknames.add("captain"); // duplicate captain + assertThat(personWrapper.isDifferent()).isTrue(); + + person.getNicknames().add("captain"); // now both have 2x "captain" but the modelWrapper has no chance to realize this change in the model element... + // ... for this reason the different flag will still be true + assertThat(personWrapper.isDifferent()).isTrue(); + + // ... but if we add another value to the nickname-Property, the modelWrapper can react to this change + person.getNicknames().add("other"); + nicknames.add("other"); + assertThat(personWrapper.isDifferent()).isFalse(); + + nicknames.add("player"); assertThat(personWrapper.isDifferent()).isTrue(); @@ -455,12 +463,23 @@ public void testDifferentFlagWithFxProperties() { nicknames.add("captain"); assertThat(personWrapper.isDifferent()).isFalse(); + + person.getNicknames().add("captain"); // duplicate value + nicknames.add("captain"); + assertThat(personWrapper.isDifferent()).isFalse(); nicknames.add("player"); assertThat(personWrapper.isDifferent()).isTrue(); - nicknames.remove("player"); + person.getNicknames().add("player"); + assertThat(personWrapper.isDifferent()).isTrue(); // still true because the modelWrapper can't detect the change in the model + + person.setName("luise"); + name.set("luise"); // this triggers the recalculation of the different-flag which will now detect the previous change to the nicknames list assertThat(personWrapper.isDifferent()).isFalse(); + + + nicknames.setValue(FXCollections.observableArrayList("spectator")); assertThat(personWrapper.isDifferent()).isTrue(); diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/Person.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/Person.java index f882a8afc..977f29cd9 100644 --- a/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/Person.java +++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/utils/mapping/Person.java @@ -1,8 +1,6 @@ package de.saxsys.mvvmfx.utils.mapping; -import javafx.collections.FXCollections; -import javafx.collections.ObservableList; - +import java.util.ArrayList; import java.util.List; public class Person { @@ -11,14 +9,14 @@ public class Person { private int age; - private ObservableList nicknames = FXCollections.observableArrayList(); + private List nicknames = new ArrayList<>(); public List getNicknames() { return nicknames; } public void setNicknames (List nicknames) { - this.nicknames.setAll(nicknames); + this.nicknames = nicknames; } public int getAge() { diff --git a/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.fxml b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.fxml new file mode 100644 index 000000000..5dc98c095 --- /dev/null +++ b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection.fxml @@ -0,0 +1,7 @@ + + + + + + diff --git a/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.fxml b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.fxml new file mode 100644 index 000000000..a59bd22a2 --- /dev/null +++ b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithoutViewModelTypeButWithInjection2.fxml @@ -0,0 +1,7 @@ + + + + + + diff --git a/pom.xml b/pom.xml index 2d21f8a53..9c29b087a 100644 --- a/pom.xml +++ b/pom.xml @@ -26,7 +26,7 @@ de.saxsys mvvmfx-parent pom - 1.4.0 + 1.4.1 mvvmFX parent Application Framework for MVVM with JavaFX. http://www.saxsys.de diff --git a/travis_snapshot_release.sh b/travis_snapshot_release.sh new file mode 100755 index 000000000..c55de2936 --- /dev/null +++ b/travis_snapshot_release.sh @@ -0,0 +1,10 @@ +#!/bin/sh +set -ev + +BRANCH=${TRAVIS_BRANCH} + +if [ "$BRANCH" = "develop" ] || [ "$BRANCH" = "release" ] +then + python addServer.py + mvn clean deploy -pl 'mvvmfx,mvvmfx-cdi,mvvmfx-guice,mvvmfx-archetype' -am -DskipTests=true --settings ~/.m2/mySettings.xml +fi \ No newline at end of file