Skip to content

Commit 3a47663

Browse files
ctruedengselzer
authored andcommitted
First cut at code generation for Computers class
1 parent 96f59c8 commit 3a47663

File tree

3 files changed

+390
-0
lines changed

3 files changed

+390
-0
lines changed

bin/generate.groovy

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
#!/usr/bin/env groovy
2+
3+
/*
4+
* #%L
5+
* SciJava Operations: a framework for reusable algorithms.
6+
* %%
7+
* Copyright (C) 2018 SciJava developers.
8+
* %%
9+
* Redistribution and use in source and binary forms, with or without
10+
* modification, are permitted provided that the following conditions are met:
11+
*
12+
* 1. Redistributions of source code must retain the above copyright notice,
13+
* this list of conditions and the following disclaimer.
14+
* 2. Redistributions in binary form must reproduce the above copyright notice,
15+
* this list of conditions and the following disclaimer in the documentation
16+
* and/or other materials provided with the distribution.
17+
*
18+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
19+
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
20+
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
21+
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
22+
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
23+
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
24+
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
25+
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
26+
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
27+
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
28+
* POSSIBILITY OF SUCH DAMAGE.
29+
* #L%
30+
*/
31+
32+
@Grab('org.apache.velocity:velocity:1.7')
33+
import org.apache.velocity.app.VelocityEngine
34+
35+
// TODO: Get path to Groovy script and make these dirs relative to that.
36+
templateDirectory = 'templates'
37+
outputDirectory = 'src'
38+
39+
knownFiles = new java.util.HashSet();
40+
41+
/* Gets the last modified timestamp for the given file. */
42+
def timestamp(dir, file) {
43+
if (file == null) return Long.MAX_VALUE;
44+
file = new java.io.File(dir, file);
45+
knownFiles.add(file);
46+
return file.lastModified();
47+
}
48+
49+
/* Processes a template using Apache Velocity. */
50+
def processTemplate(engine, context, templateFile, outFilename) {
51+
if (outFilename == null) return; // nothing to do
52+
53+
// create output directory if it does not already exist
54+
outFile = new java.io.File(outputDirectory, outFilename);
55+
knownFiles.add(outFile);
56+
if (outFile.getParentFile() != null) outFile.getParentFile().mkdirs();
57+
58+
// apply the template and write out the result
59+
t = engine.getTemplate(templateFile);
60+
writer = new StringWriter();
61+
t.merge(context, writer);
62+
out = new PrintWriter(outFile, "UTF-8");
63+
out.print(writer.toString());
64+
out.close();
65+
}
66+
67+
/* Evaluates a string using Groovy. */
68+
def parseValue(sh, translationsFile, key, expression) {
69+
try {
70+
return sh.evaluate(expression);
71+
}
72+
catch (groovy.lang.GroovyRuntimeException e) {
73+
print("[WARNING] $translationsFile: " +
74+
"key '$key' has unparseable value: " + e.getMessage());
75+
}
76+
}
77+
78+
/*
79+
* Translates a template into many files in the outputDirectory,
80+
* given a translations file in INI style; e.g.:
81+
*
82+
* [filename1]
83+
* variable1 = value1
84+
* variable2 = value2
85+
* ...
86+
* [filename2]
87+
* variable1 = value3
88+
* variable2 = value4
89+
* ...
90+
*/
91+
def translate(templateSubdirectory, templateFile, translationsFile) {
92+
// initialize the Velocity engine
93+
engine = new org.apache.velocity.app.VelocityEngine();
94+
p = new java.util.Properties();
95+
// fail if template uses an invalid expression; e.g., an undefined variable
96+
p.setProperty("runtime.references.strict", "true");
97+
// tell Velocity where the templates are located
98+
p.setProperty("file.resource.loader.path", "$templateSubdirectory");
99+
// tell Velocity to log to stderr rather than to a velocity.log file
100+
p.setProperty(org.apache.velocity.runtime.RuntimeConstants.RUNTIME_LOG_LOGSYSTEM_CLASS,
101+
"org.apache.velocity.runtime.log.SystemLogChute");
102+
engine.init(p);
103+
104+
// read translation lines
105+
context = outputFilename = null;
106+
reader = new java.io.BufferedReader(new java.io.FileReader("$templateSubdirectory/$translationsFile"));
107+
108+
// avoid rewriting unchanged code to avoid recompilation
109+
mtime = java.lang.Math.max(
110+
timestamp(templateSubdirectory, translationsFile),
111+
timestamp(templateSubdirectory, templateFile));
112+
113+
sh = new groovy.lang.GroovyShell();
114+
for (;;) {
115+
// read the line
116+
line = reader.readLine();
117+
118+
if (line == null) break;
119+
// check if the line starts a new section
120+
if (line.startsWith("[") && line.endsWith("]")) {
121+
// write out the previous file
122+
if (mtime >= timestamp(outputDirectory, outputFilename)) {
123+
processTemplate(engine, context, templateFile, outputFilename);
124+
}
125+
126+
// start a new file
127+
outputFilename = line.substring(1, line.length() - 1);
128+
if (!templateDirectory.equals(templateSubdirectory)) {
129+
subPath = templateSubdirectory.substring(templateDirectory.length() + 1);
130+
outputFilename = "$subPath/$outputFilename";
131+
}
132+
context = new org.apache.velocity.VelocityContext();
133+
continue;
134+
}
135+
136+
// ignore blank lines
137+
trimmedLine = line.trim();
138+
if (trimmedLine.isEmpty()) continue;
139+
140+
// ignore comments
141+
if (trimmedLine.startsWith("#")) continue;
142+
143+
// parse key/value pair lines separate by equals
144+
if (!line.contains('=')) {
145+
print("[WARNING] $translationsFile: Ignoring spurious line: $line");
146+
continue;
147+
}
148+
149+
int idx = line.indexOf('=');
150+
key = line.substring(0, idx).trim();
151+
value = line.substring(idx + 1);
152+
153+
if (value.trim().equals('```')) {
154+
// multi-line value
155+
builder = new StringBuilder();
156+
for (;;) {
157+
line = reader.readLine();
158+
if (line == null) {
159+
throw new RuntimeException("Unfinished value: " + builder.toString());
160+
}
161+
if (line.equals('```')) {
162+
break;
163+
}
164+
if (builder.length() > 0) {
165+
builder.append("\n");
166+
}
167+
builder.append(line);
168+
}
169+
value = builder.toString();
170+
}
171+
172+
context.put(key, parseValue(sh, translationsFile, key, value));
173+
}
174+
reader.close();
175+
176+
// process the template
177+
if (mtime >= timestamp(outputDirectory, outputFilename)) {
178+
processTemplate(engine, context, templateFile, outputFilename);
179+
}
180+
}
181+
182+
/* Recursively translates all templates in the given directory. */
183+
def translateDirectory(templateSubdirectory) {
184+
for (file in new java.io.File(templateSubdirectory).listFiles()) {
185+
if (file.isDirectory()) {
186+
// process subdirectories recursively
187+
translateDirectory(file.getPath());
188+
}
189+
else {
190+
// process Velocity template files only
191+
name = file.getName();
192+
if (!name.endsWith('.vm')) continue;
193+
prefix = name.substring(0, name.lastIndexOf('.'));
194+
translate(templateSubdirectory, name, prefix + '.list');
195+
}
196+
}
197+
}
198+
199+
def cleanStaleFiles(directory) {
200+
list = directory == null ? null : directory.listFiles();
201+
if (list == null) return;
202+
203+
for (File file : list) {
204+
if (file.isDirectory()) {
205+
cleanStaleFiles(file);
206+
} else if (file.isFile() && !knownFiles.contains(file)) {
207+
file.delete();
208+
}
209+
}
210+
}
211+
212+
try {
213+
translateDirectory(templateDirectory);
214+
cleanStaleFiles(new File(outputDirectory));
215+
}
216+
catch (Throwable t) {
217+
t.printStackTrace(System.err);
218+
throw t;
219+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
[Computers.java]
2+
arities = (0..3).collect()
3+
consumerArity = ```
4+
{ arity ->
5+
arity == 0 ? 'Consumer' :
6+
arity == 1 ? 'BiConsumer' :
7+
"Consumers.Arity${arity+1}"
8+
}
9+
```
10+
generics = ```
11+
{ arity ->
12+
arity == 0 ? '<O>' :
13+
arity == 1 ? '<I, O>' :
14+
'<' + String.join(', ', (1..arity).stream().map{a -> "I$a"}.collect()) + ', O>'
15+
}
16+
```
17+
matchParams = ```
18+
{ arity ->
19+
arity == 0 ? 'final Nil<O> outType' :
20+
arity == 1 ? 'final Nil<I> inType, final Nil<O> outType' :
21+
String.join(', ', (1..arity).stream().map{a -> "final Nil<I$a> in${a}Type"}.collect()) + ', final Nil<O> outType'
22+
}
23+
```
24+
typeArgs = ```
25+
{ arity ->
26+
arity == 0 ? 'outType.getType()' :
27+
arity == 1 ? 'inType.getType(), outType.getType()' :
28+
String.join(', ', (1..arity).stream().map{a -> "in${a}Type.getType()"}.collect()) + ', outType.getType()'
29+
}
30+
```
31+
nilArgs = ```
32+
{ arity ->
33+
arity == 0 ? 'outType' :
34+
arity == 1 ? 'inType, outType' :
35+
String.join(', ', (1..arity).stream().map{a -> "in${a}Type"}.collect()) + ', outType'
36+
}
37+
```
38+
computeParams = ```
39+
{ arity ->
40+
arity == 0 ? '@Mutable O out' :
41+
arity == 1 ? 'I in, @Mutable O out' :
42+
String.join(', ', (1..arity).stream().map{a -> "I$a in$a"}.collect()) + ', @Mutable O out'
43+
}
44+
```
45+
acceptParams = ```
46+
{ arity ->
47+
arity == 0 ? 'final O out' :
48+
arity == 1 ? 'final I in, final O out' :
49+
String.join(', ', (1..arity).stream().map{a -> "final I$a in$a"}.collect()) + ', final O out'
50+
}
51+
```
52+
computeArgs = ```
53+
{ arity ->
54+
arity == 0 ? 'out' :
55+
arity == 1 ? 'in, out' :
56+
String.join(', ', (1..arity).stream().map{a -> "in$a"}.collect()) + ', out'
57+
}
58+
```
Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* This is autogenerated source code -- DO NOT EDIT. Instead, edit the
3+
* corresponding template in templates/ and rerun bin/generate.groovy.
4+
*/
5+
6+
package org.scijava.ops.function;
7+
8+
import com.google.common.collect.BiMap;
9+
import com.google.common.collect.ImmutableBiMap;
10+
11+
import java.lang.reflect.Type;
12+
import java.util.HashMap;
13+
import java.util.Map;
14+
import java.util.function.BiConsumer;
15+
import java.util.function.Consumer;
16+
17+
import org.scijava.ops.OpService;
18+
import org.scijava.ops.types.Nil;
19+
import org.scijava.param.Mutable;
20+
import org.scijava.util.Types;
21+
22+
/**
23+
* Container class for computer-style functional interfaces at various
24+
* <a href="https://en.wikipedia.org/wiki/Arity">arities</a>.
25+
* <p>
26+
* A computer has functional method {@code compute} with a number of arguments
27+
* corresponding to the arity, plus an additional argument for the preallocated
28+
* output to be populated by the computation.
29+
* </p>
30+
* <p>
31+
* Each computer interface implements a corresponding {@link Consumer}-style
32+
* interface (see {@link Consumers}) with arity+1; the consumer's {@code accept}
33+
* method simply delegates to {@code compute}. This pattern allows computer ops
34+
* to be used directly as consumers as needed.
35+
* </p>
36+
*
37+
* @author Curtis Rueden
38+
* @author Gabriel Selzer
39+
*/
40+
public final class Computers {
41+
42+
private Computers() {
43+
// NB: Prevent instantiation of utility class.
44+
}
45+
46+
// -- BEGIN TEMP --
47+
48+
/**
49+
* All known computer types and their arities. The entries are sorted by
50+
* arity, i.e., the {@code i}-th entry has an arity of {@code i}.
51+
*/
52+
public static final BiMap<Class<?>, Integer> ALL_COMPUTERS;
53+
54+
static {
55+
final Map<Class<?>, Integer> computers = new HashMap<>();
56+
#foreach($arity in $arities)
57+
computers.put(Arity${arity}.class, $arity);
58+
#end
59+
ALL_COMPUTERS = ImmutableBiMap.copyOf(computers);
60+
}
61+
62+
/**
63+
* @return {@code true} if the given type is a {@link #ALL_COMPUTERS known}
64+
* computer type, {@code false} otherwise. <br>
65+
* Note that only the type itself and not its type hierarchy is
66+
* considered.
67+
* @throws NullPointerException If {@code type} is {@code null}.
68+
*/
69+
public static boolean isComputer(Type type) {
70+
return ALL_COMPUTERS.containsKey(Types.raw(type));
71+
}
72+
#foreach($arity in $arities)
73+
#set($rawClass = "Arity$arity")
74+
#set($genericParams = $generics.call($arity))
75+
#set($gClass = "$rawClass$genericParams")
76+
77+
public static $genericParams $gClass match(final OpService ops, final String opName,
78+
$matchParams.call($arity))
79+
{
80+
final Nil<$gClass> specialType =
81+
new Nil<$gClass>()
82+
{
83+
@Override
84+
public Type getType() {
85+
return Types.parameterize(${rawClass}.class, //
86+
new Type[] { $typeArgs.call($arity) });
87+
}
88+
};
89+
return ops.findOp( //
90+
opName, //
91+
specialType, //
92+
new Nil<?>[] { $nilArgs.call($arity) }, //
93+
outType);
94+
}
95+
#end##foreach($arity)
96+
97+
// -- END TEMP --
98+
#foreach($arity in $arities)
99+
#set($rawClass = "Arity$arity")
100+
#set($genericParams = $generics.call($arity))
101+
#set($gClass = "$rawClass$genericParams")
102+
103+
@FunctionalInterface
104+
public interface $gClass extends $consumerArity.call($arity)$genericParams {
105+
void compute($computeParams.call($arity));
106+
107+
@Override
108+
default void accept($acceptParams.call($arity)) {
109+
compute($computeArgs.call($arity));
110+
}
111+
}
112+
#end##foreach($arity)
113+
}

0 commit comments

Comments
 (0)