Skip to content

Commit ede9d55

Browse files
gselzerctrueden
authored andcommitted
Make @input and @param identical within Javadoc
This change applies only to method and class Ops, however it enables @input synonymous with @param and @output synonymous with @return. This also allows us to use @container and @mutable on Ops written as classes and as methods. This then changes the paradigm to make the parsing of @param and @return a bonus, but favors using @input and @output respectively.
1 parent c5a01c2 commit ede9d55

2 files changed

Lines changed: 234 additions & 49 deletions

File tree

scijava/scijava-ops/src/main/java/org/scijava/param/JavadocParameterData.java

Lines changed: 80 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11

22
package org.scijava.param;
33

4+
import com.github.therapi.runtimejavadoc.Comment;
45
import com.github.therapi.runtimejavadoc.FieldJavadoc;
56
import com.github.therapi.runtimejavadoc.MethodJavadoc;
67
import com.github.therapi.runtimejavadoc.OtherJavadoc;
@@ -61,34 +62,12 @@ public JavadocParameterData(Class<?> c) {
6162
* @param f the field
6263
*/
6364
public JavadocParameterData(Field f) {
64-
paramNames = new ArrayList<>();
65-
paramDescriptions = new ArrayList<>();
66-
FieldJavadoc doc = RuntimeJavadoc.getJavadoc(f);
67-
for (OtherJavadoc other : doc.getOther()) {
68-
switch (other.getName()) {
69-
case "input":
70-
String param = other.getComment().toString();
71-
paramNames.add(param.substring(0, param.indexOf(" ")));
72-
paramDescriptions.add(param.substring(param.indexOf(" ") + 1));
73-
break;
74-
case "output":
75-
if (returnDescription != null) throw new IllegalArgumentException(
76-
"Op cannot have multiple returns!");
77-
returnDescription = other.getComment().toString();
78-
break;
79-
}
80-
}
81-
82-
// ensure non-null description
83-
if (returnDescription == null) returnDescription = "";
84-
85-
// ensure that f has enough @parameter annotations
8665
Method sam = ParameterStructs.singularAbstractMethod(f.getType());
87-
int numParams = sam.getParameterCount();
88-
if (numParams != paramNames.size()) {
89-
throw new IllegalArgumentException("Field " + f +
90-
" does not have one @param annotation for each parameter of the Op!");
91-
}
66+
FieldJavadoc doc = RuntimeJavadoc.getJavadoc(f);
67+
if (hasCustomJavadoc(doc.getOther(), sam)) populateViaCustomTaglets(doc
68+
.getOther());
69+
else throw new IllegalArgumentException("Field " + f +
70+
" does not have enough taglets to generate OpInfo documentation!");
9271
}
9372

9473
public JavadocParameterData(OpInfo info, Type newType) {
@@ -163,16 +142,80 @@ private Method getOpMethod(Class<?> c) throws NoSuchMethodException {
163142
*/
164143
private void parseMethod(Method m) {
165144
MethodJavadoc doc = RuntimeJavadoc.getJavadoc(m);
166-
List<ParamJavadoc> params = doc.getParams();
167-
if (params.size() != m.getParameterCount())
168-
throw new IllegalArgumentException("Method " + m +
169-
" does not have valid @param tags for each of its inputs!");
170-
paramNames = params.stream().map(param -> param.getName() != null ? param
171-
.getName() : null).collect(Collectors.toList());
172-
paramDescriptions = params.stream().map(param -> param.getComment()
173-
.toString()).collect(Collectors.toList());
174-
returnDescription = doc.getReturns().toString();
175-
if (returnDescription == null) returnDescription = "";
145+
if (hasVanillaJavadoc(doc, m))
146+
populateViaParamAndReturn(doc.getParams(), doc.getReturns());
147+
else if (hasCustomJavadoc(doc.getOther(), m))
148+
populateViaCustomTaglets(doc.getOther());
149+
else throw new IllegalArgumentException("Method " + m +
150+
" has no suitable tag(lets) to scrape documentation from");
151+
}
152+
153+
private boolean hasVanillaJavadoc(MethodJavadoc doc, Method m) {
154+
// We require a @param tag for each of the method parameters
155+
boolean sufficientParams = doc.getParams().size() == m.getParameterCount();
156+
// We require a @return tag for the method return iff not null
157+
boolean sufficientReturn = !((doc.getReturns() != null) ^ (m
158+
.getReturnType() != void.class));
159+
return sufficientParams && sufficientReturn;
160+
}
161+
162+
private boolean hasCustomJavadoc(List<OtherJavadoc> doc, Method m) {
163+
int ins = 0, outs = 0;
164+
for (OtherJavadoc other : doc) {
165+
switch (other.getName()) {
166+
case "input":
167+
ins++;
168+
break;
169+
case "container":
170+
case "mutable":
171+
ins++;
172+
outs++;
173+
break;
174+
case "output":
175+
outs++;
176+
break;
177+
}
178+
}
179+
// We require as many input/container/mutable taglets as there are parameters
180+
boolean sufficientIns = ins == m.getParameterCount();
181+
// We require one container/mutable/output taglet
182+
boolean sufficientOuts = outs == 1;
183+
return sufficientIns && sufficientOuts;
184+
}
185+
186+
private void populateViaParamAndReturn(List<ParamJavadoc> params, Comment returnDoc) {
187+
paramNames = params.stream().map(param -> param.getName()).collect(Collectors.toList());
188+
paramDescriptions = params.stream().map(param -> param.getComment().toString()).collect(Collectors.toList());
189+
returnDescription = returnDoc.toString();
190+
}
191+
192+
private void populateViaCustomTaglets(List<OtherJavadoc> doc) {
193+
paramNames = new ArrayList<>();
194+
paramDescriptions = new ArrayList<>();
195+
for (OtherJavadoc other : doc) {
196+
String name = other.getName();
197+
if (!validParameterTag(name)) continue;
198+
// add to params if not a pure output
199+
if (!name.equals("output")) {
200+
String param = other.getComment().toString();
201+
paramNames.add(param.substring(0, param.indexOf(" ")));
202+
paramDescriptions.add(param.substring(param.indexOf(" ") + 1));
203+
}
204+
// add return description if an I/O
205+
if (!name.equals("input")) {
206+
if (returnDescription != null) throw new IllegalArgumentException(
207+
"Op cannot have multiple returns!");
208+
returnDescription = other.getComment().toString();
209+
}
210+
}
211+
}
212+
213+
private boolean validParameterTag(String tagType) {
214+
if (tagType.equals("input")) return true;
215+
if (tagType.equals("mutable")) return true;
216+
if (tagType.equals("container")) return true;
217+
if (tagType.equals("output")) return true;
218+
return false;
176219
}
177220

178221
@Override

scijava/scijava-ops/src/test/java/org/scijava/param/JavadocParameterTest.java

Lines changed: 154 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,27 +32,95 @@
3232
public class JavadocParameterTest extends AbstractTestEnvironment {
3333

3434
/**
35+
* Tests javadoc scraping with param (P) and return (R)
36+
*
3537
* @param foo the first input
3638
* @param bar the second input
3739
* @return foo + bar
3840
*/
39-
@OpMethod(names = "test.javadoc.method", type = BiFunction.class)
40-
public static List<Long> OpMethodFoo(List<String> foo, List<String> bar) {
41+
@OpMethod(names = "test.javadoc.methodPR", type = BiFunction.class)
42+
public static List<Long> OpMethodPR(List<String> foo, List<String> bar) {
43+
BiFunction<String, String, Long> func = (s1, s2) -> Long.parseLong(s1) +
44+
Long.parseLong(s2);
45+
return Streams.zip(foo.stream(), bar.stream(), func).collect(Collectors
46+
.toList());
47+
}
48+
49+
/**
50+
* Tests javadoc scraping with input (I) and output (O)
51+
*
52+
* @input foo the first input
53+
* @input bar the second input
54+
* @output foo + bar
55+
*/
56+
@OpMethod(names = "test.javadoc.methodIO", type = BiFunction.class)
57+
public static List<Long> OpMethodIO(List<String> foo, List<String> bar) {
58+
BiFunction<String, String, Long> func = (s1, s2) -> Long.parseLong(s1) +
59+
Long.parseLong(s2);
60+
return Streams.zip(foo.stream(), bar.stream(), func).collect(Collectors
61+
.toList());
62+
}
63+
64+
/**
65+
* Tests javadoc scraping with input (I) and return (R)
66+
*
67+
* @input foo the first input
68+
* @input bar the second input
69+
* @return foo + bar
70+
*/
71+
@OpMethod(names = "test.javadoc.methodIR", type = BiFunction.class)
72+
public static List<Long> OpMethodIR(List<String> foo, List<String> bar) {
4173
BiFunction<String, String, Long> func = (s1, s2) -> Long.parseLong(s1) +
4274
Long.parseLong(s2);
4375
return Streams.zip(foo.stream(), bar.stream(), func).collect(Collectors
4476
.toList());
4577
}
4678

4779
@Test
48-
public void testJavadocMethod() {
49-
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.method").iterator();
80+
public void testJavadocMethodPR() {
81+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.methodPR").iterator();
5082

5183
OpInfo info = infos.next();
5284
if (infos.hasNext()) {
5385
Assert.fail("Multiple OpInfos with name \"test.javadoc.method\"");
5486
}
87+
isSuitableScrapedOpMethodInfo(info);
88+
}
89+
90+
@Test
91+
public void testJavadocMethodIO() {
92+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.methodIO").iterator();
5593

94+
OpInfo info = infos.next();
95+
if (infos.hasNext()) {
96+
Assert.fail("Multiple OpInfos with name \"test.javadoc.method\"");
97+
}
98+
isSuitableScrapedOpMethodInfo(info);
99+
}
100+
101+
@Test
102+
public void testJavadocMethodIR() {
103+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.methodIR").iterator();
104+
105+
OpInfo info = infos.next();
106+
if (infos.hasNext()) {
107+
Assert.fail("Multiple OpInfos with name \"test.javadoc.method\"");
108+
}
109+
isSuitableGenericOpMethodInfo(info);
110+
}
111+
112+
/**
113+
* Asserts that the {@link OpInfo} has as inputs:
114+
* <ul>
115+
* <li> foo - the first input
116+
* <li> bar - the second input
117+
* </ul>
118+
* and as output:
119+
* <ul>
120+
* <li> output - foo + bar
121+
* </ul>
122+
*/
123+
private void isSuitableScrapedOpMethodInfo(OpInfo info) {
56124
// assert input names
57125
String[] inputNames = info.inputs().stream().map(m -> m.getKey()).toArray(
58126
String[]::new);
@@ -72,23 +140,66 @@ public void testJavadocMethod() {
72140
Assert.assertEquals("foo + bar", outputDescription);
73141
}
74142

143+
/**
144+
* Asserts that the {@link OpInfo} has as inputs:
145+
* <ul>
146+
* <li> input1
147+
* <li> input2
148+
* </ul>
149+
* and as output:
150+
* <ul>
151+
* <li> output1
152+
* </ul>
153+
*/
154+
private void isSuitableGenericOpMethodInfo(OpInfo info) {
155+
// assert input names
156+
String[] inputNames = info.inputs().stream().map(m -> m.getKey()).toArray(
157+
String[]::new);
158+
Assert.assertArrayEquals(inputNames, new String[] { "input1", "input2" });
159+
160+
// assert input descriptions
161+
String[] inputDescriptions = info.inputs().stream().map(m -> m.getDescription()).toArray(
162+
String[]::new);
163+
Assert.assertArrayEquals(inputDescriptions, new String[] { "", "" });
164+
165+
// assert output name
166+
String outputName = info.output().getKey();
167+
Assert.assertEquals("output1", outputName);
168+
169+
// assert output description
170+
String outputDescription = info.output().getDescription();
171+
Assert.assertEquals("", outputDescription);
172+
}
173+
75174
/**
76175
* @input in the input
77176
* @output the output
78177
*/
79-
@OpField(names = "test.javadoc.field")
178+
@OpField(names = "test.javadoc.fieldF")
80179
public final Function<Double, Double> javadocFieldOp = (in) -> in + 1;
81180

181+
/**
182+
* @input inList the input
183+
* @container outList the preallocated output
184+
*/
185+
@OpField(names = "test.javadoc.fieldC")
186+
public final Computers.Arity1<List<Double>, List<Double>> javadocFieldOpComputer = (in, out) -> {
187+
out.clear();
188+
for(Double d :in) {
189+
out.add(d + 1);
190+
}
191+
};
192+
82193
@Test
83-
public void testJavadocField() {
84-
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.field").iterator();
194+
public void testJavadocFieldF() {
195+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.fieldF").iterator();
85196

86197
if (!infos.hasNext()) {
87-
Assert.fail("No OpInfos with name \"test.javadoc.field\"");
198+
Assert.fail("No OpInfos with name \"test.javadoc.fieldF\"");
88199
}
89200
OpInfo info = infos.next();
90201
if (infos.hasNext()) {
91-
Assert.fail("Multiple OpInfos with name \"test.javadoc.field\"");
202+
Assert.fail("Multiple OpInfos with name \"test.javadoc.fieldF\"");
92203
}
93204

94205
// assert input names
@@ -109,6 +220,37 @@ public void testJavadocField() {
109220
String outputDescription = info.output().getDescription();
110221
assertEquals("the output", outputDescription);
111222
}
223+
224+
@Test
225+
public void testJavadocFieldC() {
226+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.fieldC").iterator();
227+
228+
if (!infos.hasNext()) {
229+
Assert.fail("No OpInfos with name \"test.javadoc.fieldC\"");
230+
}
231+
OpInfo info = infos.next();
232+
if (infos.hasNext()) {
233+
Assert.fail("Multiple OpInfos with name \"test.javadoc.fieldC\"");
234+
}
235+
236+
// assert input names
237+
String[] inputNames = info.inputs().stream().map(m -> m.getKey()).toArray(
238+
String[]::new);
239+
Assert.assertArrayEquals(new String[] { "inList", "outList" }, inputNames);
240+
241+
// assert input descriptions
242+
String[] inputDescriptions = info.inputs().stream().map(m -> m.getDescription()).toArray(
243+
String[]::new);
244+
Assert.assertArrayEquals(new String[] { "the input", "the preallocated output" }, inputDescriptions);
245+
246+
// assert output name
247+
String outputName = info.output().getKey();
248+
Assert.assertEquals("outList", outputName);
249+
250+
// assert output description
251+
String outputDescription = info.output().getDescription();
252+
assertEquals("the preallocated output", outputDescription);
253+
}
112254

113255
@Test
114256
public void testJavadocClass() {
@@ -143,7 +285,7 @@ public void testJavadocClass() {
143285

144286
@Test
145287
public void opStringRegressionTest() {
146-
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.method").iterator();
288+
Iterator<OpInfo> infos = ops.env().infos("test.javadoc.methodPR").iterator();
147289

148290
OpInfo info = infos.next();
149291
if (infos.hasNext()) {
@@ -153,7 +295,7 @@ public void opStringRegressionTest() {
153295
// test standard op string
154296
String expected =
155297
"public static java.util.List<java.lang.Long> org.scijava.param.JavadocParameterTest." +
156-
"OpMethodFoo(java.util.List<java.lang.String>,java.util.List<java.lang.String>)(\n" +
298+
"OpMethodPR(java.util.List<java.lang.String>,java.util.List<java.lang.String>)(\n" +
157299
" Inputs:\n" +
158300
" java.util.List<java.lang.String> foo -> the first input\n" +
159301
" java.util.List<java.lang.String> bar -> the second input\n" +
@@ -165,7 +307,7 @@ public void opStringRegressionTest() {
165307
// test special op string
166308
expected =
167309
"public static java.util.List<java.lang.Long> org.scijava.param.JavadocParameterTest." +
168-
"OpMethodFoo(java.util.List<java.lang.String>,java.util.List<java.lang.String>)(\n" +
310+
"OpMethodPR(java.util.List<java.lang.String>,java.util.List<java.lang.String>)(\n" +
169311
" Inputs:\n" +
170312
" java.util.List<java.lang.String> foo -> the first input\n" +
171313
"==> java.util.List<java.lang.String> bar -> the second input\n" +

0 commit comments

Comments
 (0)