diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/Initialize.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/Initialize.java
new file mode 100644
index 000000000..ea357794c
--- /dev/null
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/Initialize.java
@@ -0,0 +1,52 @@
+/*******************************************************************************
+ * Copyright 2017 Gleb Koval
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package de.saxsys.mvvmfx;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ *
+ * This annotation is used to mark the method in a ViewModel to be called after all mvvmFx injections.
+ * If no method is marked, public initialize()
method will be used, if present.
+ * Example:
+ *
+ *
+ *
+ * public class SomeViewModel implements {@link ViewModel} {
+ *
+ * // mvvmFx injections
+ * {@literal @}{@link InjectScope}
+ * private SomeScope someScope;
+ * ...
+ *
+ * {@literal @}Initialize
+ * private void init() {
+ * someScope.subscribe(...);
+ * ...
+ * }
+ * }
+ *
+ *
+ * @author Gleb Koval
+ *
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Initialize {
+}
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 c9b681bbb..71939838e 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
@@ -16,6 +16,7 @@
package de.saxsys.mvvmfx.internal.viewloader;
import java.lang.annotation.Annotation;
+import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -89,13 +90,13 @@ public static List getFieldsFromClassHierarchy(Class> type) {
/**
- * 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.
+ * Helper method to execute a callback on a given member. This method encapsulates the error handling logic and the
+ * handling of accessibility of the member.
*
- * After the callback is executed the accessibility of the field will be reset to the originally state.
+ * After the callback is executed the accessibility of the member will be reset to the originally state.
*
- * @param field
- * the field that is made accessible to run the callback
+ * @param member
+ * the member that is made accessible to run the callback
* @param callable
* the callback that will be executed.
* @param errorMessage
@@ -106,19 +107,19 @@ public static List getFieldsFromClassHierarchy(Class> type) {
* @throws IllegalStateException
* when something went wrong.
*/
- public static T accessField(final Field field, final Callable callable, String errorMessage) {
+ public static T accessMember(final AccessibleObject member, final Callable callable, String errorMessage) {
if (callable == null) {
return null;
}
return AccessController.doPrivileged((PrivilegedAction) () -> {
- boolean wasAccessible = field.isAccessible();
+ boolean wasAccessible = member.isAccessible();
try {
- field.setAccessible(true);
+ member.setAccessible(true);
return callable.call();
} catch (Exception exception) {
throw new IllegalStateException(errorMessage, exception);
} finally {
- field.setAccessible(wasAccessible);
+ member.setAccessible(wasAccessible);
}
});
}
@@ -136,21 +137,21 @@ public static T accessField(final Field field, final Callable callable, S
* the new value that the field should be set to.
*/
public static void setField(final Field field, Object target, Object value) {
- accessField(field, () -> field.set(target, value),
+ accessMember(field, () -> field.set(target, value),
"Cannot set the field [" + field.getName() + "] of instance [" + target + "] to value [" + value + "]");
}
/**
- * 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. The difference to
- * {@link ReflectionUtils#accessField(Field, Callable, String)} is that this method takes a callback that doesn't
+ * Helper method to execute a callback on a given member. This method encapsulates the error handling logic and the
+ * handling of accessibility of the member. The difference to
+ * {@link ReflectionUtils#accessMember(AccessibleObject, Callable, String)} is that this method takes a callback that doesn't
* return anything but only creates a sideeffect.
*
- * After the callback is executed the accessibility of the field will be reset to the originally state.
+ * After the callback is executed the accessibility of the member will be reset to the originally state.
*
- * @param field
- * the field that is made accessible to run the callback
+ * @param member
+ * the member that is made accessible to run the callback
* @param sideEffect
* the callback that will be executed.
* @param errorMessage
@@ -159,19 +160,19 @@ public static void setField(final Field field, Object target, Object value) {
* @throws IllegalStateException
* when something went wrong.
*/
- public static void accessField(final Field field, final SideEffect sideEffect, String errorMessage) {
+ public static void accessMember(final AccessibleObject member, final SideEffect sideEffect, String errorMessage) {
if (sideEffect == null) {
return;
}
- AccessController.doPrivileged((PrivilegedAction) () -> {
- boolean wasAccessible = field.isAccessible();
+ AccessController.doPrivileged((PrivilegedAction>) () -> {
+ boolean wasAccessible = member.isAccessible();
try {
- field.setAccessible(true);
+ member.setAccessible(true);
sideEffect.call();
} catch (Exception exception) {
throw new IllegalStateException(errorMessage, exception);
} finally {
- field.setAccessible(wasAccessible);
+ member.setAccessible(wasAccessible);
}
return null;
});
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtils.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtils.java
index 30fe3cbc7..a9a6286eb 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtils.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ViewLoaderReflectionUtils.java
@@ -16,6 +16,7 @@
package de.saxsys.mvvmfx.internal.viewloader;
import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.Initialize;
import de.saxsys.mvvmfx.InjectContext;
import de.saxsys.mvvmfx.InjectScope;
import de.saxsys.mvvmfx.InjectViewModel;
@@ -30,7 +31,6 @@
import javax.annotation.PostConstruct;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
@@ -166,7 +166,7 @@ public static , ViewModelType ext
Optional fieldOptional = getViewModelField(view.getClass(), viewModelType);
if (fieldOptional.isPresent()) {
Field field = fieldOptional.get();
- return ReflectionUtils.accessField(field, () -> (ViewModelType) field.get(view),
+ return ReflectionUtils.accessMember(field, () -> (ViewModelType) field.get(view),
"Can't get the viewModel of type <" + viewModelType + ">");
} else {
return null;
@@ -189,7 +189,7 @@ public static void injectViewModel(final View view, ViewModel viewModel) {
final Optional fieldOptional = getViewModelField(view.getClass(), viewModel.getClass());
if (fieldOptional.isPresent()) {
Field field = fieldOptional.get();
- ReflectionUtils.accessField(field, () -> {
+ ReflectionUtils.accessMember(field, () -> {
Object existingViewModel = field.get(view);
if (existingViewModel == null) {
field.set(view, viewModel);
@@ -258,7 +258,7 @@ public static , VM extends ViewModel> void createAn
if (fieldOptional.isPresent()) {
Field field = fieldOptional.get();
- ReflectionUtils.accessField(field, () -> {
+ ReflectionUtils.accessMember(field, () -> {
Object existingViewModel = field.get(view);
if (existingViewModel == null) {
@@ -294,7 +294,7 @@ static void createAndInjectScopes(Object viewModel, ContextImpl context) {
List scopeFields = getScopeFields(viewModel.getClass());
scopeFields.forEach(scopeField -> {
- ReflectionUtils.accessField(scopeField, () -> injectScopeIntoField(scopeField, viewModel, context),
+ ReflectionUtils.accessMember(scopeField, () -> injectScopeIntoField(scopeField, viewModel, context),
"Can't inject Scope into ViewModel <" + viewModel.getClass() + ">");
});
}
@@ -305,7 +305,7 @@ public static void injectContext(View codeBehind, ContextImpl context) {
if (contextField.isPresent()) {
Field field = contextField.get();
- ReflectionUtils.accessField(field, () -> {
+ ReflectionUtils.accessMember(field, () -> {
field.set(codeBehind, context);
}, "Can't inject Context into the view <" + codeBehind + ">");
}
@@ -372,10 +372,10 @@ public static , ViewModelType ext
}
/**
- * If a ViewModel has a method with the signature
- * public void initialize()
it will be invoked. If no such
- * method is available nothing happens.
- *
+ * If a ViewModel has a method annotated with {@link Initialize}
+ * or method with the signature public void initialize()
+ * it will be invoked. If no such method is available nothing happens.
+ *
* @param viewModel
* the viewModel that's initialize method (if available) will be
* invoked.
@@ -387,7 +387,9 @@ public static void initializeViewModel(ViewMod
return;
}
try {
- final Method initMethod = viewModel.getClass().getMethod("initialize");
+ Method annotatedMethod = getInitializeMethod(viewModel);
+ // find method annotated with @Initialize or use initialize() otherwise
+ final Method initMethod = annotatedMethod != null ? annotatedMethod : viewModel.getClass().getMethod("initialize");
// if there is a @PostConstruct annotation, throw an exception to prevent double injection
if(initMethod.isAnnotationPresent(PostConstruct.class)) {
throw new IllegalStateException(String.format("initialize method of ViewModel [%s] is annotated with @PostConstruct. " +
@@ -397,19 +399,21 @@ public static void initializeViewModel(ViewMod
"https://github.com/sialcasa/mvvmFX/wiki/Dependency-Injection#lifecycle-postconstruct", viewModel));
}
- AccessController.doPrivileged((PrivilegedAction) () -> {
- try {
- return initMethod.invoke(viewModel);
- } catch (InvocationTargetException | IllegalAccessException e) {
- throw new IllegalStateException(
- "mvvmFX wasn't able to call the initialize method of ViewModel [" + viewModel + "].", e);
- }
- });
+ ReflectionUtils.accessMember(initMethod, () -> initMethod.invoke(viewModel), "mvvmFX wasn't able to call the initialize method of ViewModel [" + viewModel + "].");
} catch (NoSuchMethodException e) {
// it's perfectly fine that a ViewModel has no initialize method.
}
}
+ private static Method getInitializeMethod(ViewModelType viewModel) {
+ for (Method method : viewModel.getClass().getDeclaredMethods()) {
+ if(method.isAnnotationPresent(Initialize.class)) {
+ return method;
+ }
+ }
+ return null;
+ }
+
/**
* This method adds listeners for the {@link SceneLifecycle}.
*/
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 20f324120..aed33a79e 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
@@ -470,4 +470,20 @@ public void testExistingViewModelWithoutInjectionInView() {
assertThat(TestViewModel.wasInitialized).isFalse();
}
+
+ /**
+ * Method annotated with {@link de.saxsys.mvvmfx.Initialize} annotation initializes the ViewModel
+ * */
+ @Test
+ public void testViewModelIsInitializedWithAnnotatatedMethod() {
+ TestViewModelWithAnnotatedInitialize.wasInitialized = false;
+
+ ViewTuple tuple
+ = FluentViewLoader.fxmlView(TestFxmlViewWithViewModelWithAnnotatedInitialize.class).load();
+
+ TestViewModelWithAnnotatedInitialize viewModel = tuple.getViewModel();
+
+ assertThat(TestViewModelWithAnnotatedInitialize.wasInitialized).isTrue();
+
+ }
}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.java
new file mode 100644
index 000000000..2358e739b
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.java
@@ -0,0 +1,32 @@
+/*******************************************************************************
+ * Copyright 2017 Gleb Koval
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package de.saxsys.mvvmfx.internal.viewloader.example;
+
+import de.saxsys.mvvmfx.FxmlView;
+import de.saxsys.mvvmfx.InjectViewModel;
+
+/**
+ * This class is used as example View class that uses ViewModel initialized with
+ * method annotated with {@link de.saxsys.mvvmfx.Initialize} annotation
+ *
+ * @author Gleb Koval
+ */
+public class TestFxmlViewWithViewModelWithAnnotatedInitialize implements FxmlView {
+
+ @InjectViewModel
+ private TestViewModelWithAnnotatedInitialize viewModel;
+
+}
diff --git a/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithAnnotatedInitialize.java b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithAnnotatedInitialize.java
new file mode 100644
index 000000000..791abc9f7
--- /dev/null
+++ b/mvvmfx/src/test/java/de/saxsys/mvvmfx/internal/viewloader/example/TestViewModelWithAnnotatedInitialize.java
@@ -0,0 +1,35 @@
+/*******************************************************************************
+ * Copyright 2017 Gleb Koval
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ ******************************************************************************/
+package de.saxsys.mvvmfx.internal.viewloader.example;
+
+import de.saxsys.mvvmfx.Initialize;
+import de.saxsys.mvvmfx.ViewModel;
+
+/**
+ * This class is used as example ViewModel class that uses init method annotated with {@link Initialize}
+ *
+ * @author Gleb Koval
+ */
+public class TestViewModelWithAnnotatedInitialize implements ViewModel {
+
+ public static boolean wasInitialized = false;
+
+ @Initialize
+ private void init() {
+ wasInitialized = true;
+ }
+
+}
diff --git a/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.fxml b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.fxml
new file mode 100644
index 000000000..7bad2467f
--- /dev/null
+++ b/mvvmfx/src/test/resources/de/saxsys/mvvmfx/internal/viewloader/example/TestFxmlViewWithViewModelWithAnnotatedInitialize.fxml
@@ -0,0 +1,6 @@
+
+
+
+
+
+