Skip to content

Commit e4c23eb

Browse files
committed
Merge branch 'develop'
2 parents 316f844 + 2904a3c commit e4c23eb

File tree

22 files changed

+531
-57
lines changed

22 files changed

+531
-57
lines changed

.github/workflows/maven.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ jobs:
1616
strategy:
1717
matrix:
1818
# test against supported LTS versions and latest
19-
java: [ 8, 11, 15 ]
19+
java: [ 8, 11, 16 ]
2020
name: OLCUT - OpenJDK ${{ matrix.java }}
2121
steps:
2222
- uses: actions/checkout@v2

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,12 +23,12 @@ Maven:
2323
<dependency>
2424
<groupId>com.oracle.labs.olcut</groupId>
2525
<artifactId>olcut-core</artifactId>
26-
<version>5.1.5</version>
26+
<version>5.1.6</version>
2727
</dependency>
2828
```
2929
or from Gradle:
3030
```groovy
31-
implementation 'com.oracle.labs.olcut:olcut-core:5.1.5'
31+
implementation 'com.oracle.labs.olcut:olcut-core:5.1.6'
3232
```
3333

3434
The `olcut-extras` artifact is designed as a small tool for developers, as such you should compile the appropriate

THIRD_PARTY_LICENSES.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
jline 3.16.0
1+
jline 3.19.0
22

33
Copyright (c) 2002-2018, the original author or authors.
44
All rights reserved.
@@ -35,7 +35,7 @@ LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
3535
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
3636
OF THE POSSIBILITY OF SUCH DAMAGE.
3737

38-
jackson-core, jackson-databind 2.11.3
38+
jackson-core, jackson-databind 2.12.2
3939

4040

4141
Apache License
@@ -240,7 +240,7 @@ jackson-core, jackson-databind 2.11.3
240240
See the License for the specific language governing permissions and
241241
limitations under the License.
242242

243-
junit 5.7.0
243+
junit 5.7.1
244244

245245
Copyright 2015-2020 the original author or authors.
246246

olcut-config-edn/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
<groupId>com.oracle.labs.olcut</groupId>
3636
<artifactId>olcut</artifactId>
3737
<version>${revision}</version>
38-
<relativePath>..</relativePath>
38+
<relativePath>../pom.xml</relativePath>
3939
</parent>
4040

4141
<name>olcut-config-edn</name>

olcut-config-json/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<groupId>com.oracle.labs.olcut</groupId>
3434
<artifactId>olcut</artifactId>
3535
<version>${revision}</version>
36-
<relativePath>..</relativePath>
36+
<relativePath>../pom.xml</relativePath>
3737
</parent>
3838
<name>olcut-config-json</name>
3939
<artifactId>olcut-config-json</artifactId>

olcut-core/pom.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<groupId>com.oracle.labs.olcut</groupId>
3434
<artifactId>olcut</artifactId>
3535
<version>${revision}</version>
36-
<relativePath>..</relativePath>
36+
<relativePath>../pom.xml</relativePath>
3737
</parent>
3838
<name>olcut-core</name>
3939
<artifactId>olcut-core</artifactId>

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

Lines changed: 106 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

@@ -1421,6 +1422,50 @@ public <T extends Configurable> T lookup(Class<T> c, ComponentListener<T> cl) {
14211422
return comps.get(0);
14221423
}
14231424

1425+
/**
1426+
* Looks up all the components of a given type, returning a map of them.
1427+
* <p>
1428+
* If the class is an interface, it returns all the configurables which implement that interface,
1429+
* if it's a concrete class then it returns only those configurables which are exactly that class.
1430+
* @param c The class of component to lookup.
1431+
* @param <T> The type of the component.
1432+
* @return A map containing all instances of the desired class this configuration manager knows about.
1433+
*/
1434+
@SuppressWarnings("unchecked") // Casts to T are implicitly checked as we use Class<T> to find the names.
1435+
public <T extends Configurable> Map<String, T> lookupAllMap(Class<T> c) {
1436+
Map<String, T> ret = new HashMap<>();
1437+
1438+
//
1439+
// If the class isn't an interface, then lookup each of the names
1440+
// in the raw property data with the given class
1441+
// name, ignoring those things marked as importable.
1442+
if(!c.isInterface()) {
1443+
String className = c.getName();
1444+
for (Map.Entry<String, ConfigurationData> e : configurationDataMap.entrySet()) {
1445+
if (e.getValue().getClassName().equals(className) &&
1446+
!e.getValue().isImportable()) {
1447+
ret.put(e.getKey(),(T)lookup(e.getKey()));
1448+
}
1449+
}
1450+
} else {
1451+
//
1452+
// If we have an interface and no registry, lookup all the
1453+
// implementing classes and return them.
1454+
for (Map.Entry<String, ConfigurationData> e : configurationDataMap.entrySet()) {
1455+
try {
1456+
Class clazz = Class.forName(e.getValue().getClassName());
1457+
if (!e.getValue().isImportable() && c.isAssignableFrom(clazz) && !clazz.isInterface()) {
1458+
ret.put(e.getKey(),(T)innerLookup(e.getKey(),null,true));
1459+
}
1460+
} catch (ClassNotFoundException ex) {
1461+
throw new PropertyException(ex,e.getKey(),"Class not found for component " + e.getKey());
1462+
}
1463+
}
1464+
}
1465+
1466+
return ret;
1467+
}
1468+
14241469
/**
14251470
* Looks up all the components of a given type, returning a list of them.
14261471
* @param c The class of component to lookup.
@@ -1474,6 +1519,66 @@ public <T extends Configurable> List<T> lookupAll(Class<T> c, ComponentListener<
14741519
return lookupAll(c);
14751520
}
14761521

1522+
/**
1523+
* Looks for a single instance of a specified Configurable in the configuration. If there is
1524+
* no such instance, this method returns null. If there is more than one instance, an
1525+
* exception is thrown. lookupSingleton can optionally include any class that may be
1526+
* assignable (a subclass or implementation) to the provided type. Note that this
1527+
* could potentially allow a user to load their own class. If the security of the
1528+
* class you're looking up is important, declare it final to prevent code insertion.
1529+
*
1530+
* @param c The Class of component to lookup.
1531+
* @param allowAssignable allow types that are assignable to the given class to match
1532+
* @param <T> The type of the component.
1533+
* @return the one instance of the desired class this configuration manager knows about or null if
1534+
* no such instance is present
1535+
* @throws PropertyException if there is more than one instance
1536+
*/
1537+
@SuppressWarnings("unchecked") // Casts to T are implicitly checked as we use Class<T> to find the names.
1538+
public <T extends Configurable> T lookupSingleton(Class<T> c, boolean allowAssignable) throws PropertyException {
1539+
1540+
List<String> instanceNames = new ArrayList<>();
1541+
for(Map.Entry<String, ConfigurationData> e : configurationDataMap.entrySet()) {
1542+
ConfigurationData rpd = e.getValue();
1543+
try {
1544+
Class pclass = Class.forName(rpd.getClassName());
1545+
if (!rpd.isImportable() &&
1546+
((allowAssignable && c.isAssignableFrom(pclass)) ||
1547+
(!allowAssignable && rpd.getClassName().equals(c.getName())))) {
1548+
instanceNames.add(e.getKey());
1549+
}
1550+
} catch(ClassNotFoundException ex) {
1551+
logger.warning(String.format("No class %s found in ConfigurationManager",
1552+
rpd.getClassName()));
1553+
}
1554+
}
1555+
1556+
//
1557+
// Check that we got only one instance and that it is not an interface.
1558+
if (instanceNames.isEmpty()) {
1559+
return null;
1560+
}
1561+
if (instanceNames.size() > 1) {
1562+
String names = instanceNames.stream().collect(Collectors.joining(", "));
1563+
throw new PropertyException("", "Multiple instances of " + c.getName() + " found in configuration: " + names);
1564+
}
1565+
1566+
String matchedName = instanceNames.get(0);
1567+
ConfigurationData cd = configurationDataMap.get(matchedName);
1568+
try {
1569+
Class matchedClass = Class.forName(cd.getClassName());
1570+
if (!matchedClass.isInterface()) {
1571+
return (T)lookup(matchedName);
1572+
} else {
1573+
throw new PropertyException("matchedName", "Cannot instantiate component with type "
1574+
+ matchedClass + " since it is an interface");
1575+
}
1576+
} catch (ClassNotFoundException e) {
1577+
throw new PropertyException(e,matchedName,"Class not found for component " + matchedName);
1578+
}
1579+
}
1580+
1581+
14771582
/**
14781583
* Gets a list of all of the component names of the components that have
14791584
* a given type. This will not instantiate the components.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ public static TreeMap<String,FieldInfo> generateFieldInfo(Class<? extends Config
203203
Object[] constants = listType.getEnumConstants();
204204
List<String> enumConstants = new ArrayList<>();
205205
for (Object o : constants) {
206-
enumConstants.add(((Enum)o).name());
206+
enumConstants.add(((Enum<?>)o).name());
207207
}
208208
fi = new FieldInfo(f.getName(),f.getType().getName(),f,configAnnotation,defaultVal,listType.getCanonicalName(),enumConstants);
209209
} else {

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

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,12 +34,15 @@
3434
import java.lang.reflect.Field;
3535
import java.lang.reflect.InvocationTargetException;
3636
import java.lang.reflect.Modifier;
37+
import java.lang.reflect.ParameterizedType;
38+
import java.lang.reflect.Type;
3739
import java.security.AccessController;
3840
import java.security.PrivilegedAction;
3941
import java.util.ArrayDeque;
4042
import java.util.ArrayList;
4143
import java.util.Arrays;
4244
import java.util.Collections;
45+
import java.util.EnumSet;
4346
import java.util.HashMap;
4447
import java.util.HashSet;
4548
import java.util.LinkedHashMap;
@@ -77,6 +80,11 @@ default public String getOptionsDescription() {
7780
return "";
7881
}
7982

83+
/**
84+
* Constructs a formatted usage string from a table of fields.
85+
* @param usageList The fields.
86+
* @return A usage string.
87+
*/
8088
public static String formatUsage(List<List<String>> usageList) {
8189
int[] maxWidth = new int[5];
8290

@@ -118,6 +126,11 @@ public static String formatUsage(List<List<String>> usageList) {
118126
return builder.toString();
119127
}
120128

129+
/**
130+
* Constructs the usage string from the supplied Options subclass.
131+
* @param options The options to construct a usage for.
132+
* @return The usage string.
133+
*/
121134
public static List<List<String>> getUsage(Class<? extends Options> options) {
122135
ArrayList<List<String>> list = new ArrayList<>();
123136
ArrayList<List<String>> optionsList = new ArrayList<>();
@@ -175,28 +188,70 @@ public static List<List<String>> getUsage(Class<? extends Options> options) {
175188
}
176189
}
177190

191+
/**
192+
* Returns a String representing the Enum constants from this class surrounded by '{', '}'
193+
* and separated by a comma and a space.
194+
* @param enumClazz The enum class to represent.
195+
* @return A String containing all the enum constants.
196+
*/
197+
public static String getEnumConstantString(Class<? extends Enum<?>> enumClazz) {
198+
Enum<?>[] constants = enumClazz.getEnumConstants();
199+
StringBuilder sb = new StringBuilder();
200+
sb.append('{');
201+
for (Enum<?> o : constants) {
202+
sb.append(o.name());
203+
sb.append(", ");
204+
}
205+
sb.replace(sb.length() - 2, sb.length(), "}");
206+
return sb.toString();
207+
}
208+
178209
public static String generateTypeDescription(Field f) {
179210
Class<?> clazz = f.getType();
180211
if (clazz.isEnum()) {
181-
Object[] constants = clazz.getEnumConstants();
182-
StringBuilder sb = new StringBuilder();
183-
sb.append("enum - {");
184-
for (Object o : constants) {
185-
sb.append(((Enum)o).name());
186-
sb.append(", ");
212+
@SuppressWarnings("unchecked") //guarded by isEnum check
213+
Class<? extends Enum<?>> enumClazz = (Class<? extends Enum<?>>) clazz;
214+
return "enum - " + getEnumConstantString(enumClazz);
215+
} else if (clazz == EnumSet.class) {
216+
Type type = f.getGenericType();
217+
if (type instanceof ParameterizedType) {
218+
ParameterizedType typeName = (ParameterizedType) type;
219+
// Should only have a single type parameter
220+
Type enumType = typeName.getActualTypeArguments()[0];
221+
try {
222+
@SuppressWarnings("unchecked") // type parameter to an enumset must be an enum
223+
Class<? extends Enum<?>> enumClazz = (Class<? extends Enum<?>>) Class.forName(enumType.getTypeName());
224+
return "EnumSet - " + getEnumConstantString(enumClazz);
225+
} catch (ClassNotFoundException e) {
226+
Logger.getLogger(Options.class.getName()).warning("Failed to load enum class '" + enumType.getTypeName() + "'");
227+
return typeName.getTypeName();
228+
}
229+
} else {
230+
return f.getGenericType().getTypeName();
187231
}
188-
sb.replace(sb.length()-2,sb.length(),"}");
189-
return sb.toString();
190232
} else {
191233
return f.getGenericType().getTypeName();
192234
}
193235
}
194236

237+
/**
238+
* Gets the fields for this option's usage string.
239+
* @param option The option annotation.
240+
* @param f The annotated field.
241+
* @param obj The parent options object, used to access the default value for this field.
242+
* @return The fields for the usage string.
243+
*/
195244
public static ArrayList<String> getOptionUsage(Option option, Field f, Options obj) {
196245
String typeString = generateTypeDescription(f);
197246
return getOptionUsage(option,f,obj,typeString);
198247
}
199248

249+
/**
250+
* Gets the usage for one of the default options (which don't have a parent options object).
251+
* @param option The option annotation.
252+
* @param type The type of the option.
253+
* @return The fields for the usage string.
254+
*/
200255
public static ArrayList<String> getOptionUsage(Option option, String type) {
201256
ArrayList<String> output = new ArrayList<>();
202257
if (option.charName() != Option.EMPTY_CHAR) {
@@ -211,6 +266,14 @@ public static ArrayList<String> getOptionUsage(Option option, String type) {
211266
return output;
212267
}
213268

269+
/**
270+
* Gets the usage fields for the supplied option.
271+
* @param option The option annotation.
272+
* @param f The field the annotation is attached to.
273+
* @param obj The parent options object, used to access the default value for this field.
274+
* @param type The type string used in the usage (may be the enum constants, or a short type descriptor).
275+
* @return The fields for the usage string.
276+
*/
214277
public static ArrayList<String> getOptionUsage(Option option, Field f, Options obj, String type) {
215278
ArrayList<String> output = new ArrayList<>();
216279
if (option.charName() != Option.EMPTY_CHAR) {

0 commit comments

Comments
 (0)