@@ -51,7 +50,7 @@ public interface ViewModel {
*
* This notification mechanism uses the {@link NotificationCenter} internally with the difference that messages send
* by this method aren't globally available. Instead they can only be received by this viewModels {@link #subscribe(String, NotificationObserver)}
- * method or when using this viewModel instance as argument to the {@link NotificationCenter#subscribe(ViewModel, String, NotificationObserver)} method.
+ * method or when using this viewModel instance as argument to the {@link NotificationCenter#subscribe(Object, String, NotificationObserver)} method.
*
*
* See {@link NotificationTestHelper} for a utility that's purpose is to simplify unit tests with notifications.
@@ -62,7 +61,7 @@ public interface ViewModel {
* to be send
*/
default void publish(String messageName, Object... payload) {
- MvvmFX.getNotificationCenter().publish(this, messageName, payload);
+ MvvmFX.getNotificationCenter().publish(System.identityHashCode(this), messageName, payload);
}
/**
@@ -75,7 +74,7 @@ default void publish(String messageName, Object... payload) {
* which should execute when the notification occurs
*/
default void subscribe(String messageName, NotificationObserver observer) {
- MvvmFX.getNotificationCenter().subscribe(this, messageName, observer);
+ MvvmFX.getNotificationCenter().subscribe(System.identityHashCode(this), messageName, observer);
}
/**
@@ -87,7 +86,7 @@ default void subscribe(String messageName, NotificationObserver observer) {
* to remove
*/
default void unsubscribe(String messageName, NotificationObserver observer) {
- MvvmFX.getNotificationCenter().unsubscribe(this, messageName, observer);
+ MvvmFX.getNotificationCenter().unsubscribe(System.identityHashCode(this), messageName, observer);
}
/**
@@ -97,6 +96,6 @@ default void unsubscribe(String messageName, NotificationObserver observer) {
* to be removed
*/
default void unsubscribe(NotificationObserver observer) {
- MvvmFX.getNotificationCenter().unsubscribe(this, observer);
+ MvvmFX.getNotificationCenter().unsubscribe(System.identityHashCode(this), observer);
}
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/ContextImpl.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/ContextImpl.java
new file mode 100644
index 000000000..396215a62
--- /dev/null
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/ContextImpl.java
@@ -0,0 +1,28 @@
+package de.saxsys.mvvmfx.internal;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.Scope;
+
+public class ContextImpl implements Context {
+
+ private final Map, Object> scopeContext;
+
+ public ContextImpl() {
+ this(new HashMap<>());
+ }
+
+ private ContextImpl(Map, Object> scopeContext) {
+ this.scopeContext = scopeContext;
+ }
+
+ public void addScopeToContext(Scope scope) {
+ scopeContext.put(scope.getClass(), scope);
+ }
+
+ public Object getScope(Class scopeType) {
+ return scopeContext.get(scopeType);
+ }
+}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/MvvmfxApplication.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/MvvmfxApplication.java
index 6c8f760c2..00055123b 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/MvvmfxApplication.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/MvvmfxApplication.java
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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;
import javafx.application.Application;
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/WeakValueHashMap.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/WeakValueHashMap.java
new file mode 100644
index 000000000..0e57a41ba
--- /dev/null
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/WeakValueHashMap.java
@@ -0,0 +1,507 @@
+package de.saxsys.mvvmfx.internal;
+/*
+ * The contents of this file are subject to the terms
+ * of the Common Development and Distribution License
+ * (the "License"). You may not use this file except
+ * in compliance with the License.
+ *
+ * You can obtain a copy of the license at
+ * glassfish/bootstrap/legal/CDDLv1.0.txt or
+ * https://glassfish.dev.java.net/public/CDDLv1.0.html.
+ * See the License for the specific language governing
+ * permissions and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL
+ * HEADER in each file and include the License file at
+ * glassfish/bootstrap/legal/CDDLv1.0.txt. If applicable,
+ * add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your
+ * own identifying information: Portions Copyright [yyyy]
+ * [name of copyright owner]
+ */
+
+/*
+ * Copyright 2005 The Apache Software Foundation.
+ *
+ * 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.
+ */
+
+
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.AbstractCollection;
+import java.util.AbstractSet;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+/**
+ * A WeakValueHashMap is implemented as a HashMap that maps keys to WeakValues. Because we don't have access to the
+ * innards of the HashMap, we have to wrap/unwrap value objects with WeakValues on every operation. Fortunately
+ * WeakValues are small, short-lived objects, so the added allocation overhead is tolerable. This implementaton directly
+ * extends java.util.HashMap.
+ *
+ * @author Markus Fuchs
+ * @see java.util.HashMap
+ * @see java.lang.ref.WeakReference
+ */
+
+public class WeakValueHashMap extends HashMap {
+
+ /* Reference queue for cleared WeakValues */
+ private final ReferenceQueue queue = new ReferenceQueue();
+
+ /**
+ * Returns the number of key-value mappings in this map.
+ *
+ *
+ * @return the number of key-value mappings in this map.
+ */
+ @Override
+ public int size() {
+ // delegate to entrySet, as super.size() also counts WeakValues
+ return entrySet().size();
+ }
+
+ /**
+ * Returns true if this map contains no key-value mappings.
+ *
+ *
+ * @return true if this map contains no key-value mappings.
+ */
+ @Override
+ public boolean isEmpty() {
+ return size() == 0;
+ }
+
+ /**
+ * Returns true if this map contains a mapping for the specified key.
+ *
+ *
+ * @param key
+ * key whose presence in this map is to be tested
+ * @return true if this map contains a mapping for the specified key.
+ */
+ @Override
+ public boolean containsKey(Object key) {
+ // need to clean up gc'ed values before invoking super method
+ processQueue();
+ return super.containsKey(key);
+ }
+
+ /**
+ * Returns true if this map maps one or more keys to the specified value.
+ *
+ *
+ * @param value
+ * value whose presence in this map is to be tested
+ * @return true if this map maps one or more keys to this value.
+ */
+ @Override
+ public boolean containsValue(Object value) {
+ return super.containsValue(WeakValue.create(value));
+ }
+
+ /**
+ * Gets the value for the given key.
+ *
+ *
+ * @param key
+ * key whose associated value, if any, is to be returned
+ * @return the value to which this map maps the specified key.
+ */
+ @Override
+ public Object get(Object key) {
+ // We don't need to remove garbage collected values here;
+ // if they are garbage collected, the get() method returns null;
+ // the next put() call with the same key removes the old value
+ // automatically so that it can be completely garbage collected
+ return getReferenceObject((WeakReference) super.get(key));
+ }
+
+ /**
+ * Puts a new (key,value) into the map.
+ *
+ *
+ * @param key
+ * key with which the specified value is to be associated.
+ * @param value
+ * value to be associated with the specified key.
+ * @return previous value associated with specified key, or null if there was no mapping for key or the value has
+ * been garbage collected by the garbage collector.
+ */
+ @Override
+ public Object put(Object key, Object value) {
+ // If the map already contains an equivalent key, the new key
+ // of a (key, value) pair is NOT stored in the map but the new
+ // value only. But as the key is strongly referenced by the
+ // map, it can not be removed from the garbage collector, even
+ // if the key becomes weakly reachable due to the old
+ // value. So, it isn't necessary to remove all garbage
+ // collected values with their keys from the map before the
+ // new entry is made. We only clean up here to distribute
+ // clean up calls on different operations.
+ processQueue();
+
+ WeakValue oldValue = (WeakValue) super.put(key, WeakValue.create(key, value, queue));
+ return getReferenceObject(oldValue);
+ }
+
+ /**
+ * Removes key and value for the given key.
+ *
+ *
+ * @param key
+ * key whose mapping is to be removed from the map.
+ * @return previous value associated with specified key, or null if there was no mapping for key or the value has
+ * been garbage collected by the garbage collector.
+ */
+ @Override
+ public Object remove(Object key) {
+ return getReferenceObject((WeakReference) super.remove(key));
+ }
+
+ /**
+ * A convenience method to return the object held by the weak reference or null if it does not exist.
+ */
+ private final Object getReferenceObject(WeakReference ref) {
+ return (ref == null) ? null : ref.get();
+ }
+
+ /**
+ * Removes all garbage collected values with their keys from the map. Since we don't know how much the
+ * ReferenceQueue.poll() operation costs, we should not call it every map operation.
+ */
+ private void processQueue() {
+ WeakValue wv = null;
+
+ while ((wv = (WeakValue) this.queue.poll()) != null) {
+ // "super" is not really necessary but use it
+ // to be on the safe side
+ super.remove(wv.key);
+ }
+ }
+
+ /* -- Helper classes -- */
+
+ /**
+ * We need this special class to keep the backward reference from the value to the key, so that we are able to
+ * remove the key if the value is garbage collected.
+ */
+ private static class WeakValue extends WeakReference {
+ /**
+ * It's the same as the key in the map. We need the key to remove the value if it is garbage collected.
+ */
+ private Object key;
+
+ private WeakValue(Object value) {
+ super(value);
+ }
+
+ /**
+ * Creates a new weak reference without adding it to a ReferenceQueue.
+ */
+ private static WeakValue create(Object value) {
+ if (value == null)
+ return null;
+ else
+ return new WeakValue(value);
+ }
+
+ private WeakValue(Object key, Object value, ReferenceQueue queue) {
+ super(value, queue);
+ this.key = key;
+ }
+
+ /**
+ * Creates a new weak reference and adds it to the given queue.
+ */
+ private static WeakValue create(Object key, Object value,
+ ReferenceQueue queue) {
+ if (value == null)
+ return null;
+ else
+ return new WeakValue(key, value, queue);
+ }
+
+ /**
+ * A WeakValue is equal to another WeakValue iff they both refer to objects that are, in turn, equal according
+ * to their own equals methods.
+ */
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+
+ if (!(obj instanceof WeakValue))
+ return false;
+
+ Object ref1 = this.get();
+ Object ref2 = ((WeakValue) obj).get();
+
+ if (ref1 == ref2)
+ return true;
+
+ if ((ref1 == null) || (ref2 == null))
+ return false;
+
+ return ref1.equals(ref2);
+ }
+
+ /**
+ *
+ */
+ @Override
+ public int hashCode() {
+ Object ref = this.get();
+
+ return (ref == null) ? 0 : ref.hashCode();
+ }
+ }
+
+ /**
+ * Internal class for entries. This class wraps/unwraps the values of the Entry objects returned from the underlying
+ * map.
+ */
+ private class Entry implements Map.Entry {
+ private final Map.Entry ent;
+ private Object value; /*
+ * Strong reference to value, so that the GC will leave it alone as long as this Entry
+ * exists
+ */
+
+ Entry(Map.Entry ent, Object value) {
+ this.ent = ent;
+ this.value = value;
+ }
+
+ @Override
+ public Object getKey() {
+ return ent.getKey();
+ }
+
+ @Override
+ public Object getValue() {
+ return value;
+ }
+
+ @Override
+ public Object setValue(Object value) {
+ // This call changes the map. Please see the comment on
+ // the put method for the correctness remark.
+ Object oldValue = this.value;
+ this.value = value;
+ ent.setValue(WeakValue.create(getKey(), value, queue));
+ return oldValue;
+ }
+
+ private boolean valEquals(Object o1, Object o2) {
+ return (o1 == null) ? (o2 == null) : o1.equals(o2);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry e = (Map.Entry) o;
+ return (valEquals(ent.getKey(), e.getKey())
+ && valEquals(value, e.getValue()));
+ }
+
+ @Override
+ public int hashCode() {
+ Object k;
+ return ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
+ ^ ((value == null) ? 0 : value.hashCode()));
+ }
+
+ }
+
+ /**
+ * Internal class for entry sets to unwrap/wrap WeakValues stored in the map.
+ */
+ private class EntrySet extends AbstractSet {
+
+ @Override
+ public Iterator iterator() {
+ // remove garbage collected elements
+ processQueue();
+
+ return new Iterator() {
+ Iterator hashIterator = hashEntrySet.iterator();
+ Entry next = null;
+
+ @Override
+ public boolean hasNext() {
+ if (hashIterator.hasNext()) {
+ // since we removed garbage collected elements,
+ // we can simply return the next entry.
+ Map.Entry ent = (Map.Entry) hashIterator.next();
+ WeakValue wv = (WeakValue) ent.getValue();
+ Object v = (wv == null) ? null : wv.get();
+ next = new Entry(ent, v);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public Object next() {
+ if ((next == null) && !hasNext())
+ throw new NoSuchElementException();
+ Entry e = next;
+ next = null;
+ return e;
+ }
+
+ @Override
+ public void remove() {
+ hashIterator.remove();
+ }
+
+ };
+ }
+
+ @Override
+ public boolean isEmpty() {
+ return !(iterator().hasNext());
+ }
+
+ @Override
+ public int size() {
+ int j = 0;
+ for (Iterator i = iterator(); i.hasNext(); i.next())
+ j++;
+ return j;
+ }
+
+ @Override
+ public boolean remove(Object o) {
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry e = (Map.Entry) o;
+ Object ek = e.getKey();
+ Object ev = e.getValue();
+ Object hv = WeakValueHashMap.this.get(ek);
+ if (hv == null) {
+ // if the map's value is null, we have to check, if the
+ // entry's value is null and the map contains the key
+ if ((ev == null) && WeakValueHashMap.this.containsKey(ek)) {
+ WeakValueHashMap.this.remove(ek);
+ return true;
+ } else {
+ return false;
+ }
+ // otherwise, simply compare the values
+ } else if (hv.equals(ev)) {
+ WeakValueHashMap.this.remove(ek);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public int hashCode() {
+ int h = 0;
+ for (Iterator i = hashEntrySet.iterator(); i.hasNext();) {
+ Map.Entry ent = (Map.Entry) i.next();
+ Object k;
+ WeakValue wv = (WeakValue) ent.getValue();
+ if (wv == null)
+ continue;
+ h += ((((k = ent.getKey()) == null) ? 0 : k.hashCode())
+ ^ wv.hashCode());
+ }
+ return h;
+ }
+
+ }
+
+ // internal helper variable, because we can't access
+ // entrySet from the superclass inside the EntrySet class
+ private Set hashEntrySet = null;
+ // stores the EntrySet instance
+ private Set entrySet = null;
+
+ /**
+ * Returns a Set view of the mappings in this map.
+ *
+ *
+ * @return a Set view of the mappings in this map.
+ */
+ @Override
+ public Set entrySet() {
+ if (entrySet == null) {
+ hashEntrySet = super.entrySet();
+ entrySet = new EntrySet();
+ }
+ return entrySet;
+ }
+
+ // stores the value collection
+ private transient Collection values = null;
+
+ /**
+ * Returns a Collection view of the values contained in this map.
+ *
+ *
+ * @return a Collection view of the values contained in this map.
+ */
+ @Override
+ public Collection values() {
+ // delegates to entrySet, because super method returns
+ // WeakValues instead of value objects
+ if (values == null) {
+ values = new AbstractCollection() {
+ @Override
+ public Iterator iterator() {
+ return new Iterator() {
+ private final Iterator i = entrySet().iterator();
+
+ @Override
+ public boolean hasNext() {
+ return i.hasNext();
+ }
+
+ @Override
+ public Object next() {
+ return ((Entry) i.next()).getValue();
+ }
+
+ @Override
+ public void remove() {
+ i.remove();
+ }
+ };
+ }
+
+ @Override
+ public int size() {
+ return WeakValueHashMap.this.size();
+ }
+
+ @Override
+ public boolean contains(Object v) {
+ return WeakValueHashMap.this.containsValue(v);
+ }
+ };
+ }
+ return values;
+ }
+
+}
+
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/DependencyInjector.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/DependencyInjector.java
index c28927626..85fe3438e 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/DependencyInjector.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/DependencyInjector.java
@@ -65,7 +65,7 @@ public void setCustomInjector(Callback, Object> callback) {
* @return
*/
@SuppressWarnings("unchecked")
- T getInstanceOf(Class extends T> type) {
+ public T getInstanceOf(Class extends T> type) {
if (isCustomInjectorDefined()) {
return (T) customInjector.call(type);
} else {
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 37cd80f46..ed8f282d6 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,296 +15,351 @@
******************************************************************************/
package de.saxsys.mvvmfx.internal.viewloader;
+import java.io.IOException;
+import java.net.URL;
+import java.util.Collection;
+import java.util.ResourceBundle;
+import java.util.function.Consumer;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.Scope;
import de.saxsys.mvvmfx.ViewModel;
import de.saxsys.mvvmfx.ViewTuple;
+import de.saxsys.mvvmfx.internal.ContextImpl;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.util.Callback;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-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}.
+ * This viewLoader is used to load views that are implementing
+ * {@link de.saxsys.mvvmfx.FxmlView}.
*
* @author manuel.mauky
*/
public class FxmlViewLoader {
-
- private static final Logger LOG = LoggerFactory.getLogger(FxmlViewLoader.class);
-
- /**
- * Load the viewTuple by it`s ViewType.
- *
- * @param viewType
- * the type of the view to be loaded.
- * @param resourceBundle
- * the resourceBundle that is passed to the {@link javafx.fxml.FXMLLoader}.
- * @param codeBehind
- * the controller instance that is passed to the {@link javafx.fxml.FXMLLoader}
- * @param root
- * 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(
- Class extends ViewType> viewType, ResourceBundle resourceBundle, ViewType codeBehind, Object root,
- ViewModelType viewModel) {
- final String pathToFXML = createFxmlPath(viewType);
- return loadFxmlViewTuple(pathToFXML, resourceBundle, codeBehind, root, viewModel);
- }
-
- /**
- * This method is used to create a String with the path to the FXML file for a given View class.
- *
- * This is done by taking the package of the view class (if any) and replace "." with "/". After that the Name of
- * the class and the file ending ".fxml" is appended.
- *
- * Example: de.saxsys.myapp.ui.MainView as view class will be transformed to "/de/saxsys/myapp/ui/MainView.fxml"
- *
- * Example 2: MainView (located in the default package) will be transformed to "/MainView.fxml"
- *
- * @param viewType
- * the view class type.
- * @return the path to the fxml file as string.
- */
- private String createFxmlPath(Class> viewType) {
- final StringBuilder pathBuilder = new StringBuilder();
-
- pathBuilder.append("/");
-
- if (viewType.getPackage() != null) {
- pathBuilder.append(viewType.getPackage().getName().replaceAll("\\.", "/"));
- pathBuilder.append("/");
- }
-
- pathBuilder.append(viewType.getSimpleName());
- pathBuilder.append(".fxml");
-
- return pathBuilder.toString();
- }
-
- /**
- * Load the viewTuple by the path of the fxml file.
- *
- * @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
- * the controller instance that is passed to the {@link javafx.fxml.FXMLLoader}
- * @param root
- * 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(
- final String resource, ResourceBundle resourceBundle, final ViewType codeBehind, final Object root,
- ViewModelType viewModel) {
- try {
-
- final FXMLLoader loader = createFxmlLoader(resource, resourceBundle, codeBehind, root, viewModel);
-
- loader.load();
-
- final ViewType loadedController = loader.getController();
- final Parent loadedRoot = loader.getRoot();
-
- if (loadedController == null) {
- throw new IOException("Could not load the controller for the View " + resource
- + " maybe your missed the fx:controller in your fxml?");
- }
-
-
- // the actually used ViewModel instance. We need this so we can return it in the ViewTuple
- ViewModelType actualViewModel;
-
- // if no existing viewModel was provided...
- if(viewModel == null) {
- // ... we try to find the created ViewModel from the codeBehind.
- // this is only possible when the codeBehind has a field for the VM and the VM was injected
- actualViewModel = ViewLoaderReflectionUtils.getExistingViewModel(loadedController);
-
- // otherwise we create a new ViewModel. This is needed because the ViewTuple has to contain a VM even if the codeBehind doesn't need one
- if (actualViewModel == null) {
- actualViewModel = ViewLoaderReflectionUtils.createViewModel(loadedController);
- }
- } else {
- actualViewModel = viewModel;
- }
-
-
- return new ViewTuple<>(loadedController, loadedRoot, actualViewModel);
-
- } catch (final IOException ex) {
- throw new RuntimeException(ex);
- }
- }
-
-
- private FXMLLoader createFxmlLoader(String resource, ResourceBundle resourceBundle, View codeBehind, Object root,
- ViewModel viewModel)
- throws IOException {
-
- // Load FXML file
- final URL location = FxmlViewLoader.class.getResource(resource);
- if (location == null) {
- throw new IOException("Error loading FXML - can't load from given resourcepath: " + resource);
- }
-
- final FXMLLoader fxmlLoader = new FXMLLoader();
-
- fxmlLoader.setRoot(root);
- fxmlLoader.setResources(resourceBundle);
- fxmlLoader.setLocation(location);
-
- // when the user provides a viewModel but no codeBehind, we need to use the custom controller factory.
- // in all other cases the default factory can be used.
- if (viewModel != null && codeBehind == null) {
- fxmlLoader.setControllerFactory(new ControllerFactoryForCustomViewModel(viewModel, resourceBundle));
- } else {
- fxmlLoader.setControllerFactory(new DefaultControllerFactory(resourceBundle));
- }
-
- // When the user provides a codeBehind instance we take care of the injection of the viewModel to this
- // controller here.
- if (codeBehind != null) {
- fxmlLoader.setController(codeBehind);
-
- if (viewModel == null) {
- handleInjection(codeBehind, resourceBundle);
- } else {
- handleInjection(codeBehind, resourceBundle, viewModel);
- }
- }
-
- return fxmlLoader;
- }
-
- /**
- * This controller factory will try to create and inject a viewModel instance to every requested controller that is
- * a view.
- */
- private static class DefaultControllerFactory implements Callback, Object> {
- private final ResourceBundle resourceBundle;
-
- public DefaultControllerFactory(ResourceBundle resourceBundle) {
- this.resourceBundle = resourceBundle;
- }
-
- @Override
- public Object call(Class> type) {
- Object controller = DependencyInjector.getInstance().getInstanceOf(type);
-
- if (controller instanceof View) {
- View codeBehind = (View) controller;
-
- handleInjection(codeBehind, resourceBundle);
- }
-
- return controller;
- }
- }
-
-
- private static void handleInjection(View codeBehind, ResourceBundle resourceBundle) {
- ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
-
- final Optional viewModelOptional = ViewLoaderReflectionUtils.createAndInjectViewModel(codeBehind);
-
- if (viewModelOptional.isPresent()) {
- final Object viewModel = viewModelOptional.get();
- if (viewModel instanceof ViewModel) {
- ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
- ViewLoaderReflectionUtils.initializeViewModel((ViewModel) viewModel);
- }
- }
- }
-
- private static void handleInjection(View codeBehind, ResourceBundle resourceBundle, ViewModel viewModel) {
- ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
-
- if (viewModel != null) {
- ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
-
- ViewLoaderReflectionUtils.injectViewModel(codeBehind, viewModel);
- }
- }
-
- /**
- * A controller factory that is used for the special case where the user provides an existing viewModel to be used
- * while loading.
- *
- * This factory will use this existing viewModel instance for injection of the first view that is
- * requested from this factory. For all later requests this factory will work the same way as the default factory
- * {@link de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoader.DefaultControllerFactory}.
- *
- * The problem we are facing here is the following: The user wants to load a specific View with a specific ViewModel
- * instance. But this root View (fxml file) can declare other sub views. Only the root View has to get the existing
- * ViewModel instance, all other sub Views have to get their ViewModels via the default way (i.e.
- * DependencyInjection or a new instance every time).
- *
- * But, from the perspective of the controller factory, when a View instance is requested, we can't know if this is
- * the root View or a sub View. How do we know when to use the existing ViewModel instance?
- *
- * To fix this we depend on the standard JavaFX behaviour of the {@link FXMLLoader}: The first instance that the
- * FXMLLoader will request from the controller factory will always be the controller for the root fxml file. In this
- * case we can use the existing ViewModel. All subsequent requests will be handled with the default behaviour.
- */
- private static class ControllerFactoryForCustomViewModel implements Callback, Object> {
-
- private boolean customViewModelInjected = false;
-
- private final ViewModel customViewModel;
-
- private final ResourceBundle resourceBundle;
-
- public ControllerFactoryForCustomViewModel(ViewModel customViewModel, ResourceBundle resourceBundle) {
- this.customViewModel = customViewModel;
- this.resourceBundle = resourceBundle;
- }
-
- @Override
- public Object call(Class> type) {
- Object controller = DependencyInjector.getInstance().getInstanceOf(type);
-
- if (controller instanceof View) {
- View codeBehind = (View) controller;
-
- if (!customViewModelInjected) {
- ResourceBundleInjector.injectResourceBundle(customViewModel, resourceBundle);
- ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
-
- ViewLoaderReflectionUtils.injectViewModel(codeBehind, customViewModel);
-
-
- customViewModelInjected = true;
- return codeBehind;
- }
-
- handleInjection(codeBehind, resourceBundle);
- }
-
- return controller;
- }
- }
+
+ private static final Logger LOG = LoggerFactory.getLogger(FxmlViewLoader.class);
+
+ /**
+ * Load the viewTuple by it`s ViewType.
+ *
+ * @param viewType
+ * the type of the view to be loaded.
+ * @param resourceBundle
+ * the resourceBundle that is passed to the
+ * {@link javafx.fxml.FXMLLoader}.
+ * @param codeBehind
+ * the controller instance that is passed to the
+ * {@link javafx.fxml.FXMLLoader}
+ * @param root
+ * 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(
+ Class extends ViewType> viewType, ResourceBundle resourceBundle, ViewType codeBehind, Object root,
+ ViewModelType viewModel, Context context, Collection providedScopes) {
+
+ final String pathToFXML = createFxmlPath(viewType);
+ return loadFxmlViewTuple(pathToFXML, resourceBundle, codeBehind, root, viewModel, context, providedScopes);
+ }
+
+ /**
+ * This method is used to create a String with the path to the FXML file for
+ * a given View class.
+ *
+ * This is done by taking the package of the view class (if any) and replace
+ * "." with "/". After that the Name of the class and the file ending
+ * ".fxml" is appended.
+ *
+ * Example: de.saxsys.myapp.ui.MainView as view class will be transformed to
+ * "/de/saxsys/myapp/ui/MainView.fxml"
+ *
+ * Example 2: MainView (located in the default package) will be transformed
+ * to "/MainView.fxml"
+ *
+ * @param viewType
+ * the view class type.
+ * @return the path to the fxml file as string.
+ */
+ private String createFxmlPath(Class> viewType) {
+ final StringBuilder pathBuilder = new StringBuilder();
+
+ pathBuilder.append("/");
+
+ if (viewType.getPackage() != null) {
+ pathBuilder.append(viewType.getPackage().getName().replaceAll("\\.", "/"));
+ pathBuilder.append("/");
+ }
+
+ pathBuilder.append(viewType.getSimpleName());
+ pathBuilder.append(".fxml");
+
+ return pathBuilder.toString();
+ }
+
+ /**
+ * Load the viewTuple by the path of the fxml file.
+ *
+ * @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
+ * the controller instance that is passed to the
+ * {@link javafx.fxml.FXMLLoader}
+ * @param root
+ * 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(
+ final String resource, ResourceBundle resourceBundle, final ViewType codeBehind, final Object root,
+ ViewModelType viewModel, Context parentContext, Collection providedScopes) {
+ try {
+
+ // FIXME Woanders hin?
+ ContextImpl context = ViewLoaderScopeUtils.prepareContext(parentContext, providedScopes);
+ //////////////////////////////////////////////////////////////////////
+
+ final FXMLLoader loader = createFxmlLoader(resource, resourceBundle, codeBehind, root, viewModel, context);
+
+ loader.load();
+
+ final ViewType loadedController = loader.getController();
+ final Parent loadedRoot = loader.getRoot();
+
+ if (loadedController == null) {
+ throw new IOException("Could not load the controller for the View " + resource
+ + " maybe your missed the fx:controller in your fxml?");
+ }
+
+ // the actually used ViewModel instance. We need this so we can
+ // return it in the ViewTuple
+ ViewModelType actualViewModel;
+
+ // FIXME CONTEXT
+
+ // if no existing viewModel was provided...
+ if (viewModel == null) {
+ // ... we try to find the created ViewModel from the codeBehind.
+ // this is only possible when the codeBehind has a field for the
+ // VM and the VM was injected
+ actualViewModel = ViewLoaderReflectionUtils.getExistingViewModel(loadedController);
+
+ // otherwise we create a new ViewModel. This is needed because
+ // the ViewTuple has to contain a VM even if
+ // the codeBehind doesn't need one
+ if (actualViewModel == null) {
+ actualViewModel = ViewLoaderReflectionUtils.createViewModel(loadedController);
+
+ // it is possible that no viewModel could be created (f.e.
+ // when no generic VM type was specified)
+ // otherwise we need to initialize the created ViewModel
+ // instance.
+ if (actualViewModel != null) {
+ ViewLoaderReflectionUtils.initializeViewModel(actualViewModel);
+ }
+ }
+ } else {
+ actualViewModel = viewModel;
+ ViewLoaderReflectionUtils.createAndInjectScopes(actualViewModel, context);
+ }
+ if (actualViewModel != null) {
+ // TODO: Create Testcase for this corner case:
+ // If this view is the root view (the one that is loaded with
+ // the FluentViewLoader)
+ // but in the view there is no injection of the ViewModel
+ // only in this case the scope has to be injected here,
+ // If the viewModel was already injected in the View, it has
+ // it's scopes already injected
+ // ViewLoaderReflectionUtils.createAndInjectScopes(actualViewModel,
+ // context);
+ }
+
+ return new ViewTuple<>(loadedController, loadedRoot, actualViewModel);
+
+ } catch (final IOException ex) {
+ throw new RuntimeException(ex);
+ }
+ }
+
+ private FXMLLoader createFxmlLoader(String resource, ResourceBundle resourceBundle, View codeBehind, Object root,
+ ViewModel viewModel, ContextImpl context) throws IOException {
+ // Load FXML file
+ final URL location = FxmlViewLoader.class.getResource(resource);
+ if (location == null) {
+ throw new IOException("Error loading FXML - can't load from given resourcepath: " + resource);
+ }
+
+ final FXMLLoader fxmlLoader = new FXMLLoader();
+
+ fxmlLoader.setRoot(root);
+ fxmlLoader.setResources(resourceBundle);
+ fxmlLoader.setLocation(location);
+
+ // when the user provides a viewModel but no codeBehind, we need to use
+ // the custom controller factory.
+ // in all other cases the default factory can be used.
+ if (viewModel != null && codeBehind == null) {
+ fxmlLoader
+ .setControllerFactory(new ControllerFactoryForCustomViewModel(viewModel, resourceBundle, context));
+ } else {
+ fxmlLoader.setControllerFactory(new DefaultControllerFactory(resourceBundle, context));
+ }
+
+ // When the user provides a codeBehind instance we take care of the
+ // injection of the viewModel to this
+ // controller here.
+ if (codeBehind != null) {
+ fxmlLoader.setController(codeBehind);
+
+ if (viewModel == null) {
+ handleInjection(codeBehind, resourceBundle, context);
+ } else {
+ handleInjection(codeBehind, resourceBundle, viewModel, context);
+ }
+ }
+
+ return fxmlLoader;
+ }
+
+ /**
+ * This controller factory will try to create and inject a viewModel
+ * instance to every requested controller that is a view.
+ */
+ private static class DefaultControllerFactory implements Callback, Object> {
+ private final ResourceBundle resourceBundle;
+ private final ContextImpl context;
+
+ public DefaultControllerFactory(ResourceBundle resourceBundle, ContextImpl context) {
+ this.resourceBundle = resourceBundle;
+ this.context = context;
+ }
+
+ @Override
+ public Object call(Class> type) {
+ Object controller = DependencyInjector.getInstance().getInstanceOf(type);
+
+ if (controller instanceof View) {
+ View codeBehind = (View) controller;
+
+ handleInjection(codeBehind, resourceBundle, context);
+ }
+
+ return controller;
+ }
+ }
+
+ private static void handleInjection(View codeBehind, ResourceBundle resourceBundle, ContextImpl context) {
+ ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
+
+ Consumer newVmConsumer = viewModel -> {
+ ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
+ ViewLoaderReflectionUtils.createAndInjectScopes(viewModel, context);
+ ViewLoaderReflectionUtils.initializeViewModel(viewModel);
+ };
+
+ ViewLoaderReflectionUtils.createAndInjectViewModel(codeBehind, newVmConsumer);
+ ViewLoaderReflectionUtils.injectContext(codeBehind, context);
+ }
+
+ private static void handleInjection(View codeBehind, ResourceBundle resourceBundle, ViewModel viewModel,
+ ContextImpl context) {
+ ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
+
+ if (viewModel != null) {
+ ResourceBundleInjector.injectResourceBundle(viewModel, resourceBundle);
+ ViewLoaderReflectionUtils.createAndInjectScopes(viewModel, context);
+ ViewLoaderReflectionUtils.injectViewModel(codeBehind, viewModel);
+ ViewLoaderReflectionUtils.injectContext(codeBehind, context);
+ }
+ }
+
+ /**
+ * A controller factory that is used for the special case where the user
+ * provides an existing viewModel to be used while loading.
+ *
+ * This factory will use this existing viewModel instance for injection of
+ * the first view that is requested from this factory. For
+ * all later requests this factory will work the same way as the default
+ * factory
+ * {@link de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoader.DefaultControllerFactory}
+ * .
+ *
+ * The problem we are facing here is the following: The user wants to load a
+ * specific View with a specific ViewModel instance. But this root View
+ * (fxml file) can declare other sub views. Only the root View has to get
+ * the existing ViewModel instance, all other sub Views have to get their
+ * ViewModels via the default way (i.e. DependencyInjection or a new
+ * instance every time).
+ *
+ * But, from the perspective of the controller factory, when a View instance
+ * is requested, we can't know if this is the root View or a sub View. How
+ * do we know when to use the existing ViewModel instance?
+ *
+ * To fix this we depend on the standard JavaFX behaviour of the
+ * {@link FXMLLoader}: The first instance that the FXMLLoader will request
+ * from the controller factory will always be the controller for the root
+ * fxml file. In this case we can use the existing ViewModel. All subsequent
+ * requests will be handled with the default behaviour.
+ */
+ private static class ControllerFactoryForCustomViewModel implements Callback, Object> {
+
+ private boolean customViewModelInjected = false;
+
+ private final ViewModel customViewModel;
+
+ private final ResourceBundle resourceBundle;
+
+ private final ContextImpl context;
+
+ public ControllerFactoryForCustomViewModel(ViewModel customViewModel, ResourceBundle resourceBundle,
+ ContextImpl context) {
+ this.customViewModel = customViewModel;
+ this.resourceBundle = resourceBundle;
+ this.context = context;
+ }
+
+ @Override
+ public Object call(Class> type) {
+ Object controller = DependencyInjector.getInstance().getInstanceOf(type);
+
+ if (controller instanceof View) {
+ View codeBehind = (View) controller;
+
+ if (!customViewModelInjected) {
+ ResourceBundleInjector.injectResourceBundle(customViewModel, resourceBundle);
+ ResourceBundleInjector.injectResourceBundle(codeBehind, resourceBundle);
+
+ ViewLoaderReflectionUtils.injectViewModel(codeBehind, customViewModel);
+
+ customViewModelInjected = true;
+ return codeBehind;
+ }
+
+ handleInjection(codeBehind, resourceBundle, context);
+ }
+
+ return controller;
+ }
+ }
}
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/JavaViewLoader.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/JavaViewLoader.java
index d31cad1ec..7ab26f884 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/JavaViewLoader.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/JavaViewLoader.java
@@ -15,84 +15,97 @@
******************************************************************************/
package de.saxsys.mvvmfx.internal.viewloader;
-import de.saxsys.mvvmfx.ViewModel;
-import de.saxsys.mvvmfx.ViewTuple;
-import javafx.fxml.Initializable;
-import javafx.scene.Parent;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
+import java.util.Collection;
import java.util.List;
import java.util.ResourceBundle;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.Scope;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.ViewTuple;
+import de.saxsys.mvvmfx.internal.ContextImpl;
+import javafx.fxml.Initializable;
+import javafx.scene.Parent;
+
/**
- * This viewLoader is used to load views that are implementing {@link de.saxsys.mvvmfx.JavaView}.
+ * This viewLoader is used to load views that are implementing
+ * {@link de.saxsys.mvvmfx.JavaView}.
*
* @author manuel.mauky
*/
public class JavaViewLoader {
- private static final Logger LOG = LoggerFactory.getLogger(JavaViewLoader.class);
-
-
- private static final String NAMING_CONVENTION_RESOURCES_IDENTIFIER = "resources";
- private static final String NAMING_CONVENTION_INITIALIZE_IDENTIFIER = "initialize";
-
- /**
- * Loads the java written view of the given type and injects the ViewModel for this view.
- * If the given view type implements the {@link javafx.fxml.Initializable} interface the "initialize" method of this
- * interface will be invoked. When this is not the case an implicit initialization will be done that is working
- * similar to the way the {@link javafx.fxml.FXMLLoader} is working.
- * When there is a public no-args method named "initialize" is available this method will be
- * called. When there is a public field of type {@link java.util.ResourceBundle} named "resources"
- * is available this field will get the provided ResourceBundle injected.
- * The "initialize" method (whether from the {@link javafx.fxml.Initializable} interface or implicit) will be
- * invoked after the viewModel was injected. This way the user can create bindings to the viewModel
- * in the initialize method.
- *
- * @param viewType
- * class of the view.
- * @param resourceBundle
- * optional ResourceBundle that will be injected into the view.
- * @param existingViewModel
- * the viewModel instance that is used to load the view.
- * @param
- * the generic type of the view.
- * @param
- * the generic type of the viewModel.
- *
- * @return a fully loaded and initialized instance of the view.
- */
- public , ViewModelType extends ViewModel> ViewTuple loadJavaViewTuple(
- Class extends ViewType>
- viewType, ResourceBundle resourceBundle, final ViewModelType existingViewModel) {
- DependencyInjector injectionFacade = DependencyInjector.getInstance();
-
- final ViewType view = injectionFacade.getInstanceOf(viewType);
-
- if (!(view instanceof Parent)) {
- throw new IllegalArgumentException("Can not load java view! The view class has to extend from "
- + Parent.class.getName() + " or one of it's subclasses");
- }
+ private static final Logger LOG = LoggerFactory.getLogger(JavaViewLoader.class);
+ private static final String NAMING_CONVENTION_RESOURCES_IDENTIFIER = "resources";
+ private static final String NAMING_CONVENTION_INITIALIZE_IDENTIFIER = "initialize";
- ViewModelType viewModel = null;
+ /**
+ * Loads the java written view of the given type and injects the ViewModel
+ * for this view.
+ * If the given view type implements the {@link javafx.fxml.Initializable}
+ * interface the "initialize" method of this interface will be invoked. When
+ * this is not the case an implicit initialization will be done that is
+ * working similar to the way the {@link javafx.fxml.FXMLLoader} is working.
+ *
+ * When there is a public no-args method named "initialize"
+ * is available this method will be called. When there is a
+ * public field of type {@link java.util.ResourceBundle}
+ * named "resources" is available this field will get the provided
+ * ResourceBundle injected.
+ * The "initialize" method (whether from the
+ * {@link javafx.fxml.Initializable} interface or implicit) will be invoked
+ * after the viewModel was injected. This way the user can
+ * create bindings to the viewModel in the initialize method.
+ *
+ * @param viewType
+ * class of the view.
+ * @param resourceBundle
+ * optional ResourceBundle that will be injected into the view.
+ * @param existingViewModel
+ * the viewModel instance that is used to load the view.
+ * @param
+ * the generic type of the view.
+ * @param
+ * the generic type of the viewModel.
+ *
+ * @return a fully loaded and initialized instance of the view.
+ */
+ public , ViewModelType extends ViewModel> ViewTuple loadJavaViewTuple(
+ Class extends ViewType> viewType, ResourceBundle resourceBundle, final ViewModelType existingViewModel,
+ ViewType codeBehind, Context parentContext, Collection providedScopes) {
+
+ // FIXME Woanders hin?!
+ ContextImpl context = ViewLoaderScopeUtils.prepareContext(parentContext, providedScopes);
+ ////////////////////////////
+
+ DependencyInjector injectionFacade = DependencyInjector.getInstance();
+
+ final ViewType view = codeBehind == null ? injectionFacade.getInstanceOf(viewType) : codeBehind;
+
+ if (!(view instanceof Parent)) {
+ throw new IllegalArgumentException("Can not load java view! The view class has to extend from "
+ + Parent.class.getName() + " or one of it's subclasses");
+ }
+ ViewModelType viewModel = null;
// when no viewmodel was provided by the user...
- if (existingViewModel == null) {
+ if (existingViewModel == null) {
// ... we create a new one (if possible)
viewModel = ViewLoaderReflectionUtils.createViewModel(view);
- } else {
+ } else {
viewModel = existingViewModel;
}
-
- ResourceBundleInjector.injectResourceBundle(view, resourceBundle);
+ ResourceBundleInjector.injectResourceBundle(view, resourceBundle);
// if no ViewModel is available...
if (viewModel == null) {
@@ -100,107 +113,120 @@ public , ViewModelType extends Vi
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.");
+ 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);
+
+ // if the user has provided an existing ViewModel, we will not
+ // (re-)initialize this existing instance
+ ViewLoaderReflectionUtils.createAndInjectScopes(viewModel, context);
+ if (existingViewModel == null) {
+ ViewLoaderReflectionUtils.initializeViewModel(viewModel);
+ }
+
ViewLoaderReflectionUtils.injectViewModel(view, viewModel);
}
-
if (view instanceof Initializable) {
- Initializable initializable = (Initializable) view;
- initializable.initialize(null, resourceBundle);
- } else {
- injectResourceBundle(view, resourceBundle);
- callInitialize(view);
- }
-
- return new ViewTuple<>(view, (Parent) view, viewModel);
- }
-
- /**
- * This method is trying to invoke the initialize method of the given view by reflection. This is done to meet the
- * conventions of the {@link javafx.fxml.FXMLLoader}. The conventions say that when there is a
- * public no-args method with the simple name "initialize" and the class does not implement the
- * {@link javafx.fxml.Initializable} interface, the initialize method will be invoked.
- * This method is package scoped for better testability.
- *
- * @param view
- * the view instance of which the initialize method will be invoked.
- * @param
- * the generic type of the view.
- */
- void callInitialize(View extends ViewModelType> view) {
- try {
- final Method initializeMethod = view.getClass().getMethod(NAMING_CONVENTION_INITIALIZE_IDENTIFIER);
-
- AccessController.doPrivileged((PrivilegedAction) () -> {
- try {
- return initializeMethod.invoke(view);
- } catch (InvocationTargetException e) {
- LOG.warn("The '{}' method of the view {} has thrown an exception!",
- NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
-
- Throwable cause = e.getCause();
- if (cause instanceof RuntimeException) {
- throw (RuntimeException) cause;
- } else {
- throw new RuntimeException(cause);
- }
- } catch (IllegalAccessException e) {
- LOG.warn("Can't invoke the '{}' method of the view {} because it is not accessible",
- NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
- }
- return null;
- });
-
- } catch (NoSuchMethodException e) {
- // This exception means that there is no initialize method declared.
- // While it's possible that the user has no such method by design,
- // normally and in most cases you need an initialize method in your view (either with Initialize interface
- // or implicit).
- // So it's likely that the user has misspelled the method name or uses a wrong naming convention.
- // For this reason we give the user the log message.
- LOG.debug("There is no '{}' method declared at the view {}", NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
- }
- }
-
- /**
- * Injects the given ResourceBundle into the given view using reflection. This is done to meet the conventions of
- * the {@link javafx.fxml.FXMLLoader}. The resourceBundle is only injected when there is a public
- * field of the type {@link java.util.ResourceBundle} named "resources".
- * This method is package scoped for better testability.
- *
- * @param view
- * the view instance that gets the resourceBundle injected.
- * @param resourceBundle
- * the resourceBundle instance that will be injected.
- * @param
- * the generic type of the view.
- */
- void injectResourceBundle(View extends ViewModelType> view,
- ResourceBundle resourceBundle) {
- try {
- Field resourcesField = view.getClass().getField(NAMING_CONVENTION_RESOURCES_IDENTIFIER);
-
- if (resourcesField.getType().isAssignableFrom(ResourceBundle.class)) {
- resourcesField.set(view, resourceBundle);
- }
- } catch (NoSuchFieldException e) {
- // This exception means that there is no field for the ResourceBundle.
- // This is no exceptional case but is normal when you don't need a resourceBundle in a specific view.
- // Therefore it's save to silently catch the exception.
- } catch (IllegalAccessException e) {
- LOG.warn("Can't inject the ResourceBundle into the view {} because the field isn't accessible", view);
- }
-
-
- }
-
-
+ Initializable initializable = (Initializable) view;
+ initializable.initialize(null, resourceBundle);
+ } else {
+ injectResourceBundle(view, resourceBundle);
+ callInitialize(view);
+ }
+
+ return new ViewTuple<>(view, (Parent) view, viewModel);
+ }
+
+ /**
+ * This method is trying to invoke the initialize method of the given view
+ * by reflection. This is done to meet the conventions of the
+ * {@link javafx.fxml.FXMLLoader}. The conventions say that when there is a
+ * public no-args method with the simple name "initialize"
+ * and the class does not implement the {@link javafx.fxml.Initializable}
+ * interface, the initialize method will be invoked.
+ * This method is package scoped for better testability.
+ *
+ * @param view
+ * the view instance of which the initialize method will be
+ * invoked.
+ * @param
+ * the generic type of the view.
+ */
+ void callInitialize(View extends ViewModelType> view) {
+ try {
+ final Method initializeMethod = view.getClass().getMethod(NAMING_CONVENTION_INITIALIZE_IDENTIFIER);
+
+ AccessController.doPrivileged((PrivilegedAction) () -> {
+ try {
+ return initializeMethod.invoke(view);
+ } catch (InvocationTargetException e) {
+ LOG.warn("The '{}' method of the view {} has thrown an exception!",
+ NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
+
+ Throwable cause = e.getCause();
+ if (cause instanceof RuntimeException) {
+ throw (RuntimeException) cause;
+ } else {
+ throw new RuntimeException(cause);
+ }
+ } catch (IllegalAccessException e) {
+ LOG.warn("Can't invoke the '{}' method of the view {} because it is not accessible",
+ NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
+ }
+ return null;
+ });
+
+ } catch (NoSuchMethodException e) {
+ // This exception means that there is no initialize method declared.
+ // While it's possible that the user has no such method by design,
+ // normally and in most cases you need an initialize method in your
+ // view (either with Initialize interface
+ // or implicit).
+ // So it's likely that the user has misspelled the method name or
+ // uses a wrong naming convention.
+ // For this reason we give the user the log message.
+ LOG.debug("There is no '{}' method declared at the view {}", NAMING_CONVENTION_INITIALIZE_IDENTIFIER, view);
+ }
+ }
+
+ /**
+ * Injects the given ResourceBundle into the given view using reflection.
+ * This is done to meet the conventions of the
+ * {@link javafx.fxml.FXMLLoader}. The resourceBundle is only injected when
+ * there is a public field of the type
+ * {@link java.util.ResourceBundle} named "resources".
+ * This method is package scoped for better testability.
+ *
+ * @param view
+ * the view instance that gets the resourceBundle injected.
+ * @param resourceBundle
+ * the resourceBundle instance that will be injected.
+ * @param
+ * the generic type of the view.
+ */
+ void injectResourceBundle(View extends ViewModelType> view,
+ ResourceBundle resourceBundle) {
+ try {
+ Field resourcesField = view.getClass().getField(NAMING_CONVENTION_RESOURCES_IDENTIFIER);
+
+ if (resourcesField.getType().isAssignableFrom(ResourceBundle.class)) {
+ resourcesField.set(view, resourceBundle);
+ }
+ } catch (NoSuchFieldException e) {
+ // This exception means that there is no field for the
+ // ResourceBundle.
+ // This is no exceptional case but is normal when you don't need a
+ // resourceBundle in a specific view.
+ // Therefore it's save to silently catch the exception.
+ } catch (IllegalAccessException e) {
+ LOG.warn("Can't inject the ResourceBundle into the view {} because the field isn't accessible", view);
+ }
+
+ }
+
}
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 836f6fe0c..c9b681bbb 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
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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;
import java.lang.annotation.Annotation;
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjector.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjector.java
index 24dcd33b5..8e387ecc0 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjector.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleInjector.java
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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;
import java.lang.reflect.Field;
diff --git a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleManager.java b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleManager.java
index 5584b13b4..f17a2213a 100644
--- a/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleManager.java
+++ b/mvvmfx/src/main/java/de/saxsys/mvvmfx/internal/viewloader/ResourceBundleManager.java
@@ -1,3 +1,18 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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;
import eu.lestard.doc.Internal;
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 0aa2a48aa..755a4e453 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
@@ -1,263 +1,403 @@
+/*******************************************************************************
+ * Copyright 2015 Alexander Casall, Manuel Mauky
+ *
+ * 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;
-import de.saxsys.mvvmfx.InjectViewModel;
-import de.saxsys.mvvmfx.ViewModel;
-import net.jodah.typetools.TypeResolver;
-
+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;
import java.util.List;
+import java.util.Map;
import java.util.Optional;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
+import de.saxsys.mvvmfx.Context;
+import de.saxsys.mvvmfx.InjectContext;
+import de.saxsys.mvvmfx.InjectScope;
+import de.saxsys.mvvmfx.InjectViewModel;
+import de.saxsys.mvvmfx.Scope;
+import de.saxsys.mvvmfx.ScopeProvider;
+import de.saxsys.mvvmfx.ViewModel;
+import de.saxsys.mvvmfx.internal.ContextImpl;
+import net.jodah.typetools.TypeResolver;
+
/**
- * This class encapsulates reflection related utility operations specific for loading of views.
+ * This class encapsulates reflection related utility operations specific for
+ * loading of views.
*
* @author manuel.mauky
*/
public class ViewLoaderReflectionUtils {
-
-
-
- /**
- * Returns the {@link java.lang.reflect.Field} of the viewModel for a given view type and viewModel type. If there
- * is no annotated field for the viewModel in the view the returned Optional will be empty.
- *
- * @param viewType
- * the type of the view
- * @param viewModelType
- * the type of the viewModel
- * @return an Optional that contains the Field when the field exists.
- */
- public static Optional getViewModelField(Class extends View> viewType, Class> viewModelType) {
- List allViewModelFields = getViewModelFields(viewType);
-
- if (allViewModelFields.isEmpty()) {
- return Optional.empty();
- }
-
- if (allViewModelFields.size() > 1) {
- throw new RuntimeException("The View <" + viewType + "> may only define one viewModel but there were <"
- + allViewModelFields.size() + "> viewModel fields with the @InjectViewModel annotation!");
- }
-
- 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 (!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);
- }
-
-
- /**
- * 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.
- * @return a list of fields.
- */
- public static List getViewModelFields(Class extends View> viewType) {
- return ReflectionUtils.getFieldsFromClassHierarchy(viewType).stream()
- .filter(field -> field.isAnnotationPresent(InjectViewModel.class))
- .collect(Collectors.toList());
- }
-
-
-
-
- /**
- * This method is used to get the ViewModel instance of a given view/codeBehind.
- *
- * @param view
- * the view instance where the viewModel will be looked for.
- * @param
- * the generic type of the View
- * @param
- * the generic type of the ViewModel
- * @return the ViewModel instance or null if no viewModel could be found.
- */
- @SuppressWarnings("unchecked")
- public static , ViewModelType extends ViewModel> ViewModelType getExistingViewModel(
- ViewType view) {
- final Class> viewModelType = TypeResolver.resolveRawArgument(View.class, view.getClass());
- Optional fieldOptional = getViewModelField(view.getClass(), viewModelType);
- if (fieldOptional.isPresent()) {
- Field field = fieldOptional.get();
- return ReflectionUtils
- .accessField(field, () -> (ViewModelType) field.get(view), "Can't get the viewModel of type <"
- + viewModelType + ">");
- } else {
- return null;
- }
- }
-
- /**
- * Injects the given viewModel instance into the given view. The injection will only happen when the class of the
- * given view has a viewModel field that fulfills all requirements for the viewModel injection (matching types, no
- * viewModel already existing ...).
- *
- * @param view
- * @param viewModel
- */
- public static void injectViewModel(final View view, ViewModel viewModel) {
- if (viewModel == null) {
- return;
- }
- final Optional fieldOptional = getViewModelField(view.getClass(), viewModel.getClass());
- if (fieldOptional.isPresent()) {
- Field field = fieldOptional.get();
- ReflectionUtils.accessField(field, () -> {
- Object existingViewModel = field.get(view);
- if (existingViewModel == null) {
- field.set(view, viewModel);
- }
- }, "Can't inject ViewModel of type <" + viewModel.getClass()
- + "> into the view <" + view + ">");
- }
- }
-
- /**
- * This method is used to create and inject the ViewModel for a given View instance. The ViewModel is only created
- * if the View has a suitable field for the ViewModel.
- *
- * If a ViewModel was created OR there was already a ViewModel set in the View, this viewModel instance is returned
- * via an Optional.
- *
- * @param view
- * the view instance.
- * @param
- * the generic type of the View.
- * @param
- * 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.
- */
- @SuppressWarnings("unchecked")
- public static , VM extends ViewModel> Optional createAndInjectViewModel(
- final V view) {
- 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
+
+ /**
+ * Returns the {@link java.lang.reflect.Field} of the viewModel for a given
+ * view type and viewModel type. If there is no annotated field for the
+ * viewModel in the view the returned Optional will be empty.
+ *
+ * @param viewType
+ * the type of the view
+ * @param viewModelType
+ * the type of the viewModel
+ * @return an Optional that contains the Field when the field exists.
+ */
+ public static Optional getViewModelField(Class extends View> viewType, Class> viewModelType) {
+ List allViewModelFields = getViewModelFields(viewType);
+
+ if (allViewModelFields.isEmpty()) {
+ return Optional.empty();
+ }
+
+ if (allViewModelFields.size() > 1) {
+ throw new RuntimeException("The View <" + viewType + "> may only define one viewModel but there were <"
+ + allViewModelFields.size() + "> viewModel fields with the @InjectViewModel annotation!");
+ }
+
+ 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 (!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);
+ }
+
+ public static List getScopeFields(Class> viewModelType) {
+ final List allScopeFields = getFieldsWithAnnotation(viewModelType, InjectScope.class);
+
+ allScopeFields.stream().forEach(field -> {
+ if (!Scope.class.isAssignableFrom(field.getType())) {
+ throw new RuntimeException("The ViewModel <" + viewModelType
+ + "> has a field annotated with @InjectScope but the type of the field doesn't implement the 'Scope' interface!");
+ }
+ });
+
+ return allScopeFields;
+ }
+
+ private static Optional getContextField(Class extends View> viewType) {
+ List allViewModelFields = getContextFields(viewType);
+
+ if (allViewModelFields.isEmpty()) {
+ return Optional.empty();
+ }
+
+ if (allViewModelFields.size() > 1) {
+ throw new RuntimeException("The View <" + viewType + "> may only define one Context but there were <"
+ + allViewModelFields.size() + "> Context fields with the @InjectContext annotation!");
+ }
+
+ Field field = allViewModelFields.get(0);
+
+ if (!field.getType().isAssignableFrom(Context.class)) {
+ throw new RuntimeException("The View <" + viewType
+ + "> has a field annotated with @InjectContext but the type of the field doesn't match the type Context. "
+ + "The actual type of the field is <" + field.getType() + ">.");
+ }
+
+ return Optional.of(field);
+ }
+
+ private static List getContextFields(Class extends View> viewType) {
+ return getFieldsWithAnnotation(viewType, InjectContext.class);
+ }
+
+ /**
+ * 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.
+ * @return a list of fields.
+ */
+ public static List getViewModelFields(Class extends View> viewType) {
+ return getFieldsWithAnnotation(viewType, InjectViewModel.class);
+ }
+
+ private static List getFieldsWithAnnotation(Class classType,
+ Class annotationType) {
+ return ReflectionUtils.getFieldsFromClassHierarchy(classType)
+ .stream()
+ .filter(field -> field.isAnnotationPresent(annotationType))
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * This method is used to get the ViewModel instance of a given
+ * view/codeBehind.
+ *
+ * @param view
+ * the view instance where the viewModel will be looked for.
+ * @param
+ * the generic type of the View
+ * @param
+ * the generic type of the ViewModel
+ * @return the ViewModel instance or null if no viewModel could be found.
+ */
+ @SuppressWarnings("unchecked")
+ public static , ViewModelType extends ViewModel> ViewModelType getExistingViewModel(
+ ViewType view) {
+ final Class> viewModelType = TypeResolver.resolveRawArgument(View.class, view.getClass());
+ Optional fieldOptional = getViewModelField(view.getClass(), viewModelType);
+ if (fieldOptional.isPresent()) {
+ Field field = fieldOptional.get();
+ return ReflectionUtils.accessField(field, () -> (ViewModelType) field.get(view),
+ "Can't get the viewModel of type <" + viewModelType + ">");
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Injects the given viewModel instance into the given view. The injection
+ * will only happen when the class of the given view has a viewModel field
+ * that fulfills all requirements for the viewModel injection (matching
+ * types, no viewModel already existing ...).
+ *
+ * @param view
+ * @param viewModel
+ */
+ public static void injectViewModel(final View view, ViewModel viewModel) {
+ if (viewModel == null) {
+ return;
+ }
+ final Optional fieldOptional = getViewModelField(view.getClass(), viewModel.getClass());
+ if (fieldOptional.isPresent()) {
+ Field field = fieldOptional.get();
+ ReflectionUtils.accessField(field, () -> {
+ Object existingViewModel = field.get(view);
+ if (existingViewModel == null) {
+ field.set(view, viewModel);
+ }
+ }, "Can't inject ViewModel of type <" + viewModel.getClass() + "> into the view <" + view + ">");
+ }
+ }
+
+ /**
+ * This method is used to create and inject the ViewModel for a given View
+ * instance.
+ *
+ * The following checks are done:
+ *