Skip to content

Commit 137a7e3

Browse files
authored
Added lookupSingleton convenience method to ConfigurationManager (#21)
* Added lookupSingleton convenience method to ConfigurationManager for getting a mandated single instance of a class from a configuration * Fixed typo in comment * Fixed copyright date since I modified ConfigurationManager
1 parent d7d70b1 commit 137a7e3

File tree

4 files changed

+147
-3
lines changed

4 files changed

+147
-3
lines changed

olcut-core/src/main/java/com/oracle/labs/mlrg/olcut/config/ConfigurationManager.java

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (c) 2004-2020, Oracle and/or its affiliates.
2+
* Copyright (c) 2004-2021, Oracle and/or its affiliates.
33
*
44
* Licensed under the 2-clause BSD license.
55
*
@@ -83,6 +83,7 @@
8383
import java.util.logging.Logger;
8484
import java.util.regex.Matcher;
8585
import java.util.regex.Pattern;
86+
import java.util.stream.Collectors;
8687

8788
import static com.oracle.labs.mlrg.olcut.config.PropertySheet.StoredFieldType;
8889

@@ -1474,6 +1475,66 @@ public <T extends Configurable> List<T> lookupAll(Class<T> c, ComponentListener<
14741475
return lookupAll(c);
14751476
}
14761477

1478+
/**
1479+
* Looks for a single instance of a specified Configurable in the configuration. If there is
1480+
* no such instance, this method returns null. If there is more than one instance, an
1481+
* exception is thrown. lookupSingleton can optionally include any class that may be
1482+
* assignable (a subclass or implementation) to the provided type. Note that this
1483+
* could potentially allow a user to load their own class. If the security of the
1484+
* class you're looking up is important, declare it final to prevent code insertion.
1485+
*
1486+
* @param c The Class of component to lookup.
1487+
* @param allowAssignable allow types that are assignable to the given class to match
1488+
* @param <T> The type of the component.
1489+
* @return the one instance of the desired class this configuration manager knows about or null if
1490+
* no such instance is present
1491+
* @throws PropertyException if there is more than one instance
1492+
*/
1493+
@SuppressWarnings("unchecked") // Casts to T are implicitly checked as we use Class<T> to find the names.
1494+
public <T extends Configurable> T lookupSingleton(Class<T> c, boolean allowAssignable) throws PropertyException {
1495+
1496+
List<String> instanceNames = new ArrayList<>();
1497+
for(Map.Entry<String, ConfigurationData> e : configurationDataMap.entrySet()) {
1498+
ConfigurationData rpd = e.getValue();
1499+
try {
1500+
Class pclass = Class.forName(rpd.getClassName());
1501+
if (!rpd.isImportable() &&
1502+
((allowAssignable && c.isAssignableFrom(pclass)) ||
1503+
(!allowAssignable && rpd.getClassName().equals(c.getName())))) {
1504+
instanceNames.add(e.getKey());
1505+
}
1506+
} catch(ClassNotFoundException ex) {
1507+
logger.warning(String.format("No class %s found in ConfigurationManager",
1508+
rpd.getClassName()));
1509+
}
1510+
}
1511+
1512+
//
1513+
// Check that we got only one instance and that it is not an interface.
1514+
if (instanceNames.isEmpty()) {
1515+
return null;
1516+
}
1517+
if (instanceNames.size() > 1) {
1518+
String names = instanceNames.stream().collect(Collectors.joining(", "));
1519+
throw new PropertyException("", "Multiple instances of " + c.getName() + " found in configuration: " + names);
1520+
}
1521+
1522+
String matchedName = instanceNames.get(0);
1523+
ConfigurationData cd = configurationDataMap.get(matchedName);
1524+
try {
1525+
Class matchedClass = Class.forName(cd.getClassName());
1526+
if (!matchedClass.isInterface()) {
1527+
return (T)lookup(matchedName);
1528+
} else {
1529+
throw new PropertyException("matchedName", "Cannot instantiate component with type "
1530+
+ matchedClass + " since it is an interface");
1531+
}
1532+
} catch (ClassNotFoundException e) {
1533+
throw new PropertyException(e,matchedName,"Class not found for component " + matchedName);
1534+
}
1535+
}
1536+
1537+
14771538
/**
14781539
* Gets a list of all of the component names of the components that have
14791540
* a given type. This will not instantiate the components.

olcut-core/src/test/java/com/oracle/labs/mlrg/olcut/config/ConfigurationManagerTest.java

Lines changed: 52 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,19 +28,69 @@
2828

2929
package com.oracle.labs.mlrg.olcut.config;
3030

31+
import com.oracle.labs.mlrg.olcut.config.test.Ape;
32+
import com.oracle.labs.mlrg.olcut.config.test.Barbary;
33+
import com.oracle.labs.mlrg.olcut.config.test.Chimp;
34+
import com.oracle.labs.mlrg.olcut.config.test.Gorilla;
35+
import com.oracle.labs.mlrg.olcut.config.test.Monkey;
36+
import com.oracle.labs.mlrg.olcut.config.test.Orangutan;
37+
import org.junit.jupiter.api.BeforeAll;
3138
import org.junit.jupiter.api.Test;
3239

3340
import java.util.ArrayList;
3441
import java.util.List;
3542

36-
import static org.junit.jupiter.api.Assertions.assertEquals;
37-
import static org.junit.jupiter.api.Assertions.assertNotNull;
43+
import static org.junit.jupiter.api.Assertions.*;
3844

3945
/**
4046
*
4147
*/
4248
public class ConfigurationManagerTest {
49+
private static ConfigurationManager singletonCM;
4350

51+
@BeforeAll
52+
public static void setup() {
53+
singletonCM = new ConfigurationManager("/com/oracle/labs/mlrg/olcut/config/singletonConfig.xml");
54+
}
55+
56+
@Test
57+
public void testSingletonTrue() {
58+
Chimp c = singletonCM.lookupSingleton(Chimp.class, false);
59+
assertNotNull(c, "Failed to find actual singleton");
60+
c = singletonCM.lookupSingleton(Chimp.class, true);
61+
assertNotNull(c, "Failed to find actual singleton");
62+
}
63+
64+
@Test
65+
public void testSingletonTrueSubclass() {
66+
Orangutan o = singletonCM.lookupSingleton(Orangutan.class, true);
67+
assertNotNull(o, "Failed to find Bornean as subclass");
68+
o = singletonCM.lookupSingleton(Orangutan.class, false);
69+
assertNull(o, "Should not have found an Orangutan ");
70+
}
71+
72+
73+
@Test
74+
public void testSingletonMultipleClasses() {
75+
assertThrows(PropertyException.class, () -> {singletonCM.lookupSingleton(Gorilla.class, false);},
76+
"Config has multiple Gorillas but no exception was thrown");
77+
assertThrows(PropertyException.class, () -> {singletonCM.lookupSingleton(Gorilla.class, true);},
78+
"Config has multiple Gorillas but no exception was thrown");
79+
}
80+
81+
@Test
82+
public void testSingletonMultipleInterfaces() {
83+
assertThrows(PropertyException.class, () -> {singletonCM.lookupSingleton(Monkey.class, true);},
84+
"Config has multiple Gorillas but no exception was thrown");
85+
Monkey m = singletonCM.lookupSingleton(Monkey.class, false);
86+
assertNull(m, "Looked up interface without allowAssignable but didn't get null");
87+
}
88+
89+
@Test
90+
public void testSingletonNoImport() {
91+
assertNull(singletonCM.lookupSingleton(Barbary.class, false), "Should ignore imports");
92+
assertNull(singletonCM.lookupSingleton(Barbary.class, true), "Should ignore imports");
93+
}
4494

4595
@Test
4696
public void testMultipleLoading() {
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package com.oracle.labs.mlrg.olcut.config.test;
2+
3+
public class Bornean extends Orangutan {
4+
@Override
5+
public String getMonkeyName() {
6+
return "Bornean Orangutan";
7+
}
8+
9+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
<?xml version="1.0" encoding="US-ASCII"?>
2+
3+
4+
<config>
5+
6+
<component name="chimp" type="com.oracle.labs.mlrg.olcut.config.test.Chimp">
7+
</component>
8+
9+
<component name="kingKong" type="com.oracle.labs.mlrg.olcut.config.test.Gorilla">
10+
</component>
11+
12+
<component name="grodd" type="com.oracle.labs.mlrg.olcut.config.test.Gorilla">
13+
</component>
14+
15+
<component name="harlow" type="com.oracle.labs.mlrg.olcut.config.test.Rhesus">
16+
</component>
17+
18+
<component name="bornie" type="com.oracle.labs.mlrg.olcut.config.test.Bornean">
19+
</component>
20+
21+
<component name="barbie" type="com.oracle.labs.mlrg.olcut.config.test.Barbary" import="true">
22+
</component>
23+
24+
</config>

0 commit comments

Comments
 (0)