Skip to content

Commit 67e9bf2

Browse files
authored
Merge pull request #50 from plantbreeding/develop
Release 0.53
2 parents f8c11f9 + 6a6a8c3 commit 67e9bf2

File tree

21 files changed

+564
-145
lines changed

21 files changed

+564
-145
lines changed

java/core/src/main/java/org/brapi/schematools/core/brapischema/BrAPISchemaReader.java

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public Response<List<BrAPIClass>> readDirectories(Path schemaDirectory) {
8989
*/
9090
public Response<BrAPIClass> readSchema(Path schemaPath, String module) throws BrAPISchemaReaderException {
9191
try {
92-
return new Reader().createBrAPISchemas(schemaPath, module).mapResult(list -> list.get(0));
92+
return new Reader().createBrAPISchemas(schemaPath, module).mapResult(List::getFirst);
9393
} catch (RuntimeException e) {
9494
throw new BrAPISchemaReaderException(e);
9595
}
@@ -115,6 +115,8 @@ public Response<List<BrAPIClass>> readSchema(Path path, String schema, String mo
115115

116116
private class Reader {
117117

118+
private final Map<Path, JsonNode> schemaNodes = new HashMap<>();
119+
118120
private Response<List<BrAPIClass>> readDirectories(Path schemaDirectory) {
119121

120122
try (Stream<Path> schemas = find(schemaDirectory, 3, this::schemaPathMatcher)) {
@@ -126,6 +128,22 @@ private Response<List<BrAPIClass>> readDirectories(Path schemaDirectory) {
126128
}
127129
}
128130

131+
private Response<List<BrAPIClass>> createBrAPISchemas(Path path, JsonNode json, String module) {
132+
JsonNode defs = json.get("$defs");
133+
134+
if (defs != null) {
135+
json = defs;
136+
}
137+
138+
Iterator<Map.Entry<String, JsonNode>> iterator = json.fields();
139+
140+
return Stream.generate(() -> null)
141+
.takeWhile(x -> iterator.hasNext())
142+
.map(n -> iterator.next())
143+
.map(entry -> createBrAPIClass(path, entry.getValue(), entry.getKey(), module))
144+
.collect(Response.toList());
145+
}
146+
129147
private Response<List<BrAPIClass>> dereferenceAndValidate(Response<List<BrAPIClass>> types) {
130148
return types.mapResultToResponse(this::dereferenceAll).mapResultToResponse(this::validate);
131149
}
@@ -343,22 +361,6 @@ private Response<JsonNode> findSchema(Path path) {
343361
}
344362
}
345363

346-
private final Map<Path, JsonNode> schemaNodes = new HashMap<>();
347-
348-
private Response<List<BrAPIClass>> createBrAPISchemas(Path path, JsonNode json, String module) {
349-
JsonNode defs = json.get("$defs");
350-
351-
if (defs != null) {
352-
json = defs;
353-
}
354-
355-
Iterator<Map.Entry<String, JsonNode>> iterator = json.fields();
356-
357-
return Stream.generate(() -> null)
358-
.takeWhile(x -> iterator.hasNext())
359-
.map(n -> iterator.next()).map(entry -> createBrAPIClass(path, entry.getValue(), entry.getKey(), module)).collect(Response.toList());
360-
}
361-
362364
private Response<BrAPIClass> createBrAPIClass(Path path, JsonNode jsonNode, String fallbackName, String module) {
363365
try {
364366
return createType(path, jsonNode, fallbackName, module).mapResult(type -> (BrAPIClass) type);
@@ -556,6 +558,8 @@ private Response<List<BrAPIObjectProperty>> createProperties(Path path, String n
556558

557559
private Response<List<BrAPIObjectProperty>> validateProperties(Path path, String objectName, List<BrAPIObjectProperty> properties) {
558560
Set<String> seenNames = new HashSet<>();
561+
LinkedList<BrAPIObjectProperty> duplicateProperties = new LinkedList<>();
562+
559563
for (BrAPIObjectProperty property : properties) {
560564
if (!seenNames.add(property.getName())) {
561565
if (options.isWarningAboutDuplicateProperties()) {
@@ -569,9 +573,15 @@ private Response<List<BrAPIObjectProperty>> validateProperties(Path path, String
569573
if (!options.isIgnoringDuplicateProperties()) {
570574
return Response.fail(Response.ErrorType.VALIDATION, path,
571575
String.format("Duplicate property name '%s' found in properties list for '%s'", property.getName(), objectName));
576+
} else {
577+
// Ignore duplicate by removing it
578+
duplicateProperties.add(property);
572579
}
573580
}
574581
}
582+
583+
properties.removeAll(duplicateProperties);
584+
575585
return Response.success(properties);
576586
}
577587

java/core/src/main/java/org/brapi/schematools/core/utils/StringUtils.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -404,9 +404,17 @@ public static String extractFirstLine(String description) {
404404
return description;
405405
}
406406
String cleaned = description.replace("\r", "").replace("\n", " ");
407-
int endIdx = cleaned.indexOf('.');
408-
if (endIdx != -1) {
409-
return cleaned.substring(0, endIdx + 1).trim();
407+
int bracketDepth = 0;
408+
int parenDepth = 0;
409+
for (int i = 0; i < cleaned.length(); i++) {
410+
char c = cleaned.charAt(i);
411+
if (c == '[') bracketDepth++;
412+
if (c == ']') bracketDepth = Math.max(0, bracketDepth - 1);
413+
if (c == '(') parenDepth++;
414+
if (c == ')') parenDepth = Math.max(0, parenDepth - 1);
415+
if (c == '.' && bracketDepth == 0 && parenDepth == 0) {
416+
return cleaned.substring(0, i + 1).trim();
417+
}
410418
}
411419
return cleaned.trim();
412420
}

java/core/src/main/resources/RTemplates/BrAPIClient.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ BrAPIClient <- R6Class(
1515
[# th:each="className,iterStat : ${classNames}"]
1616
#' @field [[${functionNames[iterStat.index]}]]
1717
#' Get the [[${className}]] R6 class object which can be used to call the BrAPI server for
18-
#' [${entityNames[iterStat.index]}]] entities
18+
#' [[${entityNames[iterStat.index]}]] entities
1919
[[${functionNames[iterStat.index]}]] = function() {
2020
if (is.null(private$.[[${functionNames[iterStat.index]}]])) {
2121
if (private$.verbosity > 0) {

java/core/src/test/java/org/brapi/schematools/core/brapischema/BrAPISchemaReaderTest.java

Lines changed: 98 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,21 @@
11
package org.brapi.schematools.core.brapischema;
22

3+
import lombok.extern.slf4j.Slf4j;
34
import org.brapi.schematools.core.model.BrAPIClass;
5+
import org.brapi.schematools.core.model.BrAPIObjectProperty;
6+
import org.brapi.schematools.core.model.BrAPIObjectType;
7+
import org.brapi.schematools.core.response.Response;
48
import org.junit.jupiter.api.Test;
59

610
import java.nio.charset.Charset;
711
import java.nio.file.Files;
812
import java.nio.file.Path;
913
import java.nio.file.Paths;
14+
import java.util.HashSet;
1015
import java.util.List;
1116
import java.util.Map;
1217
import java.util.Objects;
18+
import java.util.Set;
1319
import java.util.function.Function;
1420
import java.util.stream.Collectors;
1521

@@ -20,6 +26,7 @@
2026
import static org.junit.jupiter.api.Assertions.assertTrue;
2127
import static org.junit.jupiter.api.Assertions.fail;
2228

29+
@Slf4j
2330
class BrAPISchemaReaderTest {
2431

2532
@Test
@@ -77,7 +84,7 @@ void readDirectories() {
7784
assertFalse(listRequest.getMetadata().isPrimaryModel());
7885

7986
} catch (Exception e) {
80-
e.printStackTrace();
87+
log.error(e.getMessage(), e);
8188
fail(e.getMessage());
8289
}
8390
}
@@ -121,7 +128,7 @@ void readSchemaPath() {
121128
assertNotNull(listRequest.getMetadata());
122129
assertTrue(listRequest.getMetadata().isRequest());
123130
} catch (Exception e) {
124-
e.printStackTrace();
131+
log.error(e.getMessage(), e);
125132
fail(e.getMessage());
126133
}
127134
}
@@ -149,7 +156,7 @@ void readSchemaStringTrial() {
149156
assertNotNull(trialSchema.getMetadata());
150157
assertTrue(trialSchema.getMetadata().isPrimaryModel());
151158
} catch (Exception e) {
152-
e.printStackTrace();
159+
log.error(e.getMessage(), e);
153160
fail(e.getMessage());
154161
}
155162
}
@@ -176,7 +183,7 @@ void readSchemaStringStudy() {
176183
assertNotNull(studySchema.getMetadata());
177184
assertTrue(studySchema.getMetadata().isPrimaryModel());
178185
} catch (Exception e) {
179-
e.printStackTrace();
186+
log.error(e.getMessage(), e);
180187
fail(e.getMessage());
181188
}
182189
}
@@ -205,7 +212,7 @@ void readSchemaStringListType() {
205212
assertNull(listTypeSchema.getMetadata());
206213

207214
} catch (Exception e) {
208-
e.printStackTrace();
215+
log.error(e.getMessage(), e);
209216
fail(e.getMessage());
210217
}
211218
}
@@ -233,9 +240,94 @@ void readSchemaStringListRequest() {
233240
assertTrue(listRequest.getMetadata().isRequest());
234241
assertFalse(listRequest.getMetadata().isPrimaryModel());
235242
} catch (Exception e) {
236-
e.printStackTrace();
243+
log.error(e.getMessage(), e);
237244
fail(e.getMessage());
238245
}
239246
}
240247

248+
@Test
249+
void readPlateRequest() {
250+
// this should work
251+
try {
252+
Path path = Paths.get(Objects.requireNonNull(this.getClass().getResource("/BrAPI-Schema/Requests/PlateRequest.json")).toURI());
253+
254+
List<BrAPIClass> schemas = new BrAPISchemaReader().
255+
readSchema(path, String.join(System.lineSeparator(), Files.readAllLines(path, Charset.defaultCharset())), "BrAPI-Genotyping").
256+
onFailDoWithResponse(response -> fail(response.getMessagesCombined(","))).
257+
getResult();
258+
259+
assertNotNull(schemas);
260+
261+
assertEquals(1, schemas.size());
262+
263+
BrAPIClass plateRequestSchema = schemas.getFirst();
264+
265+
assertNotNull(plateRequestSchema);
266+
assertEquals("PlateRequest", plateRequestSchema.getName());
267+
assertEquals("BrAPI-Genotyping", plateRequestSchema.getModule());
268+
assertNotNull(plateRequestSchema.getMetadata());
269+
assertTrue(plateRequestSchema.getMetadata().isRequest());
270+
} catch (Exception e) {
271+
log.error(e.getMessage(), e);
272+
fail(e.getMessage());
273+
}
274+
}
275+
276+
@Test
277+
void readPlateRequestWithDuplicateProperties() {
278+
// this should fail, so will pass :)
279+
try {
280+
Path path = Paths.get(Objects.requireNonNull(this.getClass().getResource("/BrAPISchemaReader/DuplicateParameters")).toURI());
281+
282+
Response<List<BrAPIClass>> response = new BrAPISchemaReader().
283+
readDirectories(path) ;
284+
285+
assertTrue(response.hasErrors());
286+
287+
assertEquals(1, response.getAllErrors().size());
288+
} catch (Exception e) {
289+
log.error(e.getMessage(), e);
290+
fail(e.getMessage());
291+
}
292+
}
293+
294+
@Test
295+
void readPlateRequestWithDuplicatePropertiesIgnored() {
296+
// this should pass
297+
try {
298+
Path path = Paths.get(Objects.requireNonNull(this.getClass().getResource("/BrAPISchemaReader/DuplicateParameters")).toURI());
299+
300+
List<BrAPIClass> schemas = new BrAPISchemaReader(BrAPISchemaReaderOptions.load().setIgnoreDuplicateProperties(true)).
301+
readDirectories(path)
302+
.onFailDoWithResponse(response -> fail(response.getMessagesCombined(",")))
303+
.getResult();
304+
305+
assertNotNull(schemas);
306+
307+
assertEquals(6, schemas.size());
308+
309+
Map<String, BrAPIClass> map = schemas.stream().collect(Collectors.toMap(BrAPIClass::getName, Function.identity())) ;
310+
311+
BrAPIClass plateRequestSchema = map.get("PlateRequest");
312+
313+
assertNotNull(plateRequestSchema);
314+
assertEquals("PlateRequest", plateRequestSchema.getName());
315+
assertNotNull(plateRequestSchema.getMetadata());
316+
assertTrue(plateRequestSchema.getMetadata().isRequest());
317+
318+
Set<String> seenNames = new HashSet<>();
319+
320+
if (plateRequestSchema instanceof BrAPIObjectType brapiObjectType) {
321+
for (BrAPIObjectProperty property : brapiObjectType.getProperties()) {
322+
if (!seenNames.add(property.getName())) {
323+
fail("Duplicate property name found: " + property.getName());
324+
}
325+
}
326+
}
327+
328+
} catch (Exception e) {
329+
log.error(e.getMessage(), e);
330+
fail(e.getMessage());
331+
}
332+
}
241333
}

java/core/src/test/java/org/brapi/schematools/core/r/RGeneratorTest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,14 @@ private void assertREquals(List<Path> result) {
5555
Files.createDirectories(build.getParent());
5656
Files.writeString(build, actual);
5757
}
58+
}
59+
60+
for (Path path : result) {
61+
62+
String rFile = path.getFileName().toString();
63+
64+
String expected = StringUtils.readStringFromPath(Path.of(ClassLoader.getSystemResource("RGenerator/Generated/"+ rFile).toURI())).getResultOrThrow();
65+
String actual = StringUtils.readStringFromPath(path).getResultOrThrow();
5866

5967
assertMultilineEqual(expected, actual);
6068
}

java/core/src/test/java/org/brapi/schematools/core/utils/StringUtilsTest.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,5 +228,16 @@ void testExtractFirstLine() {
228228
assertEquals("", StringUtils.extractFirstLine(""));
229229
// Null input
230230
assertNull(StringUtils.extractFirstLine(null));
231+
// Period inside square brackets
232+
assertEquals("This is a test [with a period. inside brackets].", StringUtils.extractFirstLine("This is a test [with a period. inside brackets]. This is after."));
233+
// Period inside parentheses
234+
assertEquals("Sentence (with a period. inside parens).", StringUtils.extractFirstLine("Sentence (with a period. inside parens). Next sentence."));
235+
// Periods inside both types of brackets
236+
assertEquals("Start [a.b (c.d)].", StringUtils.extractFirstLine("Start [a.b (c.d)]. End."));
237+
// Multiple nested brackets
238+
assertEquals("A [b (c.d [e.f] g.h) i.j] k.", StringUtils.extractFirstLine("A [b (c.d [e.f] g.h) i.j] k. End."));
239+
// Periods inside and outside brackets
240+
assertEquals("First (ignore. this) sentence.", StringUtils.extractFirstLine("First (ignore. this) sentence. Second sentence."));
231241
}
242+
232243
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"$defs": {
3+
"CommonCropNamesParameters": {
4+
"type": "object",
5+
"properties": {
6+
"commonCropNames": {
7+
"description": "The BrAPI Common Crop Name is the simple, generalized, widely accepted name of the organism being researched. It is most often used in multi-crop systems where digital resources need to be divided at a high level. Things like 'Maize', 'Wheat', and 'Rice' are examples of common crop names.\n\nUse this parameter to only return results associated with the given crops. \n\nUse `GET /commoncropnames` to find the list of available crops on a server.",
8+
"items": {
9+
"type": "string"
10+
},
11+
"type": "array",
12+
"example": [
13+
"Tomatillo",
14+
"Paw Paw"
15+
]
16+
}
17+
},
18+
"brapi-metadata": {
19+
"parameters": true
20+
}
21+
}
22+
},
23+
"$id": "https://brapi.org/Specification/BrAPI-Schema/Requests/Parameters/CommonCropNamesParameters.json",
24+
"$schema": "http://json-schema.org/draft/2020-12/schema"
25+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
{
2+
"$defs": {
3+
"GermplasmParameters": {
4+
"type": "object",
5+
"properties": {
6+
"germplasmDbIds": {
7+
"description": "List of IDs which uniquely identify germplasm to search for",
8+
"items": {
9+
"type": "string"
10+
},
11+
"type": "array",
12+
"example": [
13+
"e9c6edd7",
14+
"1b1df4a6"
15+
]
16+
},
17+
"germplasmNames": {
18+
"description": "List of human readable names to identify germplasm to search for",
19+
"items": {
20+
"type": "string"
21+
},
22+
"type": "array",
23+
"example": [
24+
"A0000003",
25+
"A0000477"
26+
]
27+
}
28+
},
29+
"brapi-metadata": {
30+
"parameters": true
31+
}
32+
}
33+
},
34+
"$id": "https://brapi.org/Specification/BrAPI-Schema/Requests/Parameters/GermplasmParameters.json",
35+
"$schema": "http://json-schema.org/draft/2020-12/schema"
36+
}

0 commit comments

Comments
 (0)