Skip to content

Commit

Permalink
Merge pull request #243 from sialcasa/234_global_resource_bundle
Browse files Browse the repository at this point in the history
#234 global resource bundle
  • Loading branch information
manuel-mauky committed May 15, 2015
2 parents 711d87a + 30e0091 commit c407a7f
Show file tree
Hide file tree
Showing 20 changed files with 516 additions and 4 deletions.
21 changes: 18 additions & 3 deletions mvvmfx/src/main/java/de/saxsys/mvvmfx/FluentViewLoader.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import de.saxsys.mvvmfx.internal.viewloader.FxmlViewLoader;
import de.saxsys.mvvmfx.internal.viewloader.JavaViewLoader;
import de.saxsys.mvvmfx.internal.viewloader.ResourceBundleManager;

/**
* Fluent API for loading Views. <br>
Expand Down Expand Up @@ -60,8 +61,15 @@ public static class JavaViewStep<ViewType extends JavaView<? extends ViewModelTy
JavaViewStep(Class<? extends ViewType> viewType) {
this.viewType = viewType;
}

/**
* Provide a {@link ResourceBundle} that is used while loading this view.
* Note: It is possible to provide a global application-wide resourceBundle via {@link MvvmFX#setGlobalResourceBundle(ResourceBundle)} method.
*
* If there is a global resourceBundle set it will be merged with the resourceBundle provided by this builder method.
* The resourceBundle provided by this method will have a higher priority then the global one which means that if there
* are duplicate keys, the values of the global resourceBundle will be overwritten and the values of this resourceBundle will be used.
*
* @param resourceBundle
* the resource bundle that is used while loading the view.
* @return this instance of the builder step.
Expand Down Expand Up @@ -94,7 +102,7 @@ public JavaViewStep<ViewType, ViewModelType> viewModel(ViewModelType viewModel)
public ViewTuple<ViewType, ViewModelType> load() {
JavaViewLoader javaViewLoader = new JavaViewLoader();

return javaViewLoader.loadJavaViewTuple(viewType, resourceBundle, viewModel);
return javaViewLoader.loadJavaViewTuple(viewType, ResourceBundleManager.getInstance().mergeWithGlobal(resourceBundle), viewModel);
}
}

Expand All @@ -121,6 +129,13 @@ public static class FxmlViewStep<ViewType extends FxmlView<? extends ViewModelTy
}

/**
* Provide a {@link ResourceBundle} that is used while loading this view.
* Note: It is possible to provide a global application-wide resourceBundle via {@link MvvmFX#setGlobalResourceBundle(ResourceBundle)} method.
*
* If there is a global resourceBundle set it will be merged with the resourceBundle provided by this builder method.
* The resourceBundle provided by this method will have a higher priority then the global one which means that if there
* are duplicate keys, the values of the global resourceBundle will be overwritten and the values of this resourceBundle will be used.
*
* @param resourceBundle
* the resource bundle that is used while loading the view.
* @return this instance of the builder step.
Expand Down Expand Up @@ -181,7 +196,7 @@ public FxmlViewStep<ViewType, ViewModelType> viewModel(ViewModelType viewModel)
public ViewTuple<ViewType, ViewModelType> load() {
FxmlViewLoader fxmlViewLoader = new FxmlViewLoader();

return fxmlViewLoader.loadFxmlViewTuple(viewType, resourceBundle, codeBehind, root, viewModel);
return fxmlViewLoader.loadFxmlViewTuple(viewType, ResourceBundleManager.getInstance().mergeWithGlobal(resourceBundle), codeBehind, root, viewModel);
}
}

Expand Down
19 changes: 19 additions & 0 deletions mvvmfx/src/main/java/de/saxsys/mvvmfx/MvvmFX.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,15 @@
******************************************************************************/
package de.saxsys.mvvmfx;

import de.saxsys.mvvmfx.internal.viewloader.ResourceBundleManager;
import javafx.util.Callback;

import de.saxsys.mvvmfx.utils.notifications.NotificationCenter;
import de.saxsys.mvvmfx.utils.notifications.NotificationCenterFactory;
import de.saxsys.mvvmfx.internal.viewloader.DependencyInjector;

import java.util.ResourceBundle;

/**
* This class is a facade that is used by the user to access classes and services from the framework.
*/
Expand All @@ -46,4 +49,20 @@ public static NotificationCenter getNotificationCenter() {
public static void setCustomDependencyInjector(final Callback<Class<?>, Object> injector) {
DependencyInjector.getInstance().setCustomInjector(injector);
}

/**
* This method is used to set a global {@link ResourceBundle} for the application.
*
* This resource bundle is automatically loaded for all views. If there is an resourceBundle provided
* while loading the view (via {@link FluentViewLoader.FxmlViewStep#resourceBundle(ResourceBundle)} or
* {@link FluentViewLoader.JavaViewStep#resourceBundle(ResourceBundle)}) both resourceBundles will be merged.
* <p>
* The global resourceBundle set by this method will have a lower priority then the ones provided while loading.
* If there are keys available in both resourceBundles, the values of the global resourceBundle will be overwritten.
*
* @param resourceBundle the resourceBundle
*/
public static void setGlobalResourceBundle(ResourceBundle resourceBundle) {
ResourceBundleManager.getInstance().setGlobalResourceBundle(resourceBundle);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package de.saxsys.mvvmfx.internal.viewloader;

import eu.lestard.doc.Internal;
import sun.util.ResourceBundleEnumeration;

import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.ListResourceBundle;
import java.util.Map;
import java.util.Objects;
import java.util.ResourceBundle;
import java.util.Set;

/**
* @author manuel.mauky
*/
@Internal
public class ResourceBundleManager {

private static final ResourceBundleManager SINGLETON = new ResourceBundleManager();
private ResourceBundle globalResourceBundle;

ResourceBundleManager() {
}

public static ResourceBundleManager getInstance() {
return SINGLETON;
}

public void setGlobalResourceBundle(ResourceBundle resourceBundle) {
this.globalResourceBundle = resourceBundle;
}

public ResourceBundle getGlobalResourceBundle() {
return globalResourceBundle;
}


/**
* Merges the provided ResourceBundle with the global one (if any).
*
* The global resourceBundle has a lower priority then the provided one. If there is the same key defined in the
* global and the provided resourceBundle, the value from the provided resourceBundle will be used.
*
* @param resourceBundle
* a resourceBundle that will be merged.
* @return the merged resourceBundle or null, if there is no global resource bundle and not given resource bundle.
*/
public ResourceBundle mergeWithGlobal(ResourceBundle resourceBundle) {
if (globalResourceBundle == null) {
if (resourceBundle == null) {
return null;
} else {
return resourceBundle;
}
} else {
if (resourceBundle == null) {
return globalResourceBundle;
} else {
return merge(resourceBundle, globalResourceBundle);
}
}
}

private ResourceBundle merge(ResourceBundle highPriority, ResourceBundle lowPriority) {
return new MergedResourceBundle(highPriority, lowPriority);
}

private static class MergedResourceBundle extends ResourceBundle {
private ResourceBundle highPriority;
private ResourceBundle lowPriority;

public MergedResourceBundle(ResourceBundle highPriority, ResourceBundle lowPriority) {
Objects.nonNull(highPriority);
Objects.nonNull(lowPriority);
this.highPriority = highPriority;
this.lowPriority = lowPriority;
}

@Override
protected Object handleGetObject(String key) {
if (highPriority.containsKey(key)) {
return highPriority.getObject(key);
}

if (lowPriority.containsKey(key)) {
return lowPriority.getObject(key);
}

return null;
}

@Override
public Enumeration<String> getKeys() {
return new MergedEnumeration(highPriority.keySet(), lowPriority.keySet());
}
}

private static class MergedEnumeration implements Enumeration<String> {

final Iterator<String> iterator;

public MergedEnumeration(Set<String> highPriority, Set<String> lowPriority) {
Set<String> allElements = new HashSet<>();
allElements.addAll(highPriority);
allElements.addAll(lowPriority);

iterator = allElements.iterator();
}

@Override
public boolean hasMoreElements() {
return iterator.hasNext();
}

@Override
public String nextElement() {
return iterator.next();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package de.saxsys.mvvmfx.internal.viewloader;

import org.junit.Before;
import org.junit.Test;

import java.util.ListResourceBundle;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.fail;

/**
* @author manuel.mauky
*/
public class ResourceBundleManagerTest {

private static final String KEY_COMMON = "key_common";
private static final String KEY_GLOBAL_SPECIFIC = "key_global";
private static final String KEY_OTHER_SPECIFIC = "key_other";

private static final String VALUE_1_GLOBAL = "global1";
private static final String VALUE_2_GLOBAL = "global2";

private static final String VALUE_1_OTHER = "other1";
private static final String VALUE_2_OTHER = "other2";


private ResourceBundleManager manager;

private ResourceBundle global;
private ResourceBundle other;

@Before
public void setup(){
manager = new ResourceBundleManager();

global = new ListResourceBundle() {
@Override
protected Object[][] getContents() {
return new Object[][] {
{ KEY_COMMON, VALUE_1_GLOBAL},
{ KEY_GLOBAL_SPECIFIC, VALUE_2_GLOBAL}
};
}
};

other = new ListResourceBundle() {
@Override
protected Object[][] getContents() {
return new Object[][] {
{ KEY_COMMON, VALUE_1_OTHER},
{ KEY_OTHER_SPECIFIC, VALUE_2_OTHER}
};
}
};
}


@Test
public void globalAndOtherExist() {

manager.setGlobalResourceBundle(global);

final ResourceBundle merged = manager.mergeWithGlobal(other);

// the common value is used from the other bundle.
assertThat(merged.getString(KEY_COMMON)).isEqualTo(VALUE_1_OTHER);

// merged bundle contains specific values from both bundles
assertThat(merged.getString(KEY_GLOBAL_SPECIFIC)).isEqualTo(VALUE_2_GLOBAL);
assertThat(merged.getString(KEY_OTHER_SPECIFIC)).isEqualTo(VALUE_2_OTHER);
}


@Test
public void noGlobalDefined() {

final ResourceBundle merged = manager.mergeWithGlobal(other);

assertThat(merged.getString(KEY_COMMON)).isEqualTo(VALUE_1_OTHER);

assertThat(merged.getString(KEY_OTHER_SPECIFIC)).isEqualTo(VALUE_2_OTHER);

expectMissingResource(merged, KEY_GLOBAL_SPECIFIC);
}

@Test
public void otherBundleIsNull() {
manager.setGlobalResourceBundle(global);

final ResourceBundle merged = manager.mergeWithGlobal(null);

assertThat(merged.getString(KEY_COMMON)).isEqualTo(VALUE_1_GLOBAL);

assertThat(merged.getString(KEY_GLOBAL_SPECIFIC)).isEqualTo(VALUE_2_GLOBAL);
expectMissingResource(merged, KEY_OTHER_SPECIFIC);
}

@Test
public void bothBundlesAreNull() {
final ResourceBundle merged = manager.mergeWithGlobal(null);

assertThat(merged).isNull();
}


private void expectMissingResource(ResourceBundle bundle, String key) {
try {
bundle.getString(key);
fail("Expected MissingResourceException");
} catch (MissingResourceException e){
assertThat(e).hasMessageContaining(key);
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.saxsys.mvvmfx.resourcebundle.global;

import de.saxsys.javafx.test.JfxRunner;
import de.saxsys.mvvmfx.FluentViewLoader;
import de.saxsys.mvvmfx.MvvmFX;
import de.saxsys.mvvmfx.ViewTuple;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.ResourceBundle;

import static org.assertj.core.api.Assertions.assertThat;

/**
* Test global resource bundles. See https://github.com/sialcasa/mvvmFX/issues/234.
*
* @author manuel.mauky
*/
@RunWith(JfxRunner.class)
public class GlobalResourceBundleTest {


private ResourceBundle global;
private ResourceBundle other;

@Before
public void setup(){
global = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".global");
other = ResourceBundle.getBundle(this.getClass().getPackage().getName() + ".other");
}

@Test
public void test() {
MvvmFX.setGlobalResourceBundle(global);

final ViewTuple<TestView, TestViewModel> viewTuple = FluentViewLoader.fxmlView(TestView.class).resourceBundle(other).load();
final TestView codeBehind = viewTuple.getCodeBehind();

assertThat(codeBehind.resources).isNotNull();

assertThat(codeBehind.global_label.getText()).isEqualTo("global");
assertThat(codeBehind.other_label.getText()).isEqualTo("other");

// both "global" and "other" are containing the key "label".
// in this case "other" has the higher priority and overwrites the value defined in "global"
assertThat(codeBehind.label.getText()).isEqualTo("other");
}


}
Loading

0 comments on commit c407a7f

Please sign in to comment.