Skip to content

Commit b233d6a

Browse files
committed
Fixes #17 and some errors during validation. Minor refactoring.
1 parent 912b18a commit b233d6a

File tree

14 files changed

+367
-74
lines changed

14 files changed

+367
-74
lines changed

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/functions/FunctionTest.java

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,14 @@
11
package io.github.digitalsmile.gpio.functions;
22

33
import io.github.digitalsmile.annotation.NativeMemory;
4-
import io.github.digitalsmile.annotation.NativeMemoryOptions;
54
import io.github.digitalsmile.annotation.function.*;
65
import io.github.digitalsmile.annotation.NativeMemoryException;
6+
import io.github.digitalsmile.annotation.library.NativeFunction;
7+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
78
import io.github.digitalsmile.annotation.types.interfaces.NativeMemoryLayout;
89

9-
@NativeMemory(library = "3333")
10-
@NativeFunctions({
11-
@NativeFunction(name = "123", javaName = "123")
12-
})
10+
import java.util.List;
11+
1312
public interface FunctionTest {
1413
@NativeManualFunction(name = "ioctl", useErrno = true)
1514
int callByValue(int fd, long command, long data) throws NativeMemoryException;

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libcurl/Libcurl.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
import io.github.digitalsmile.annotation.NativeMemoryOptions;
66
import io.github.digitalsmile.annotation.function.ByAddress;
77
import io.github.digitalsmile.annotation.function.NativeManualFunction;
8+
import io.github.digitalsmile.annotation.library.NativeFunction;
9+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
810
import io.github.digitalsmile.annotation.structure.Enum;
911
import io.github.digitalsmile.annotation.structure.Enums;
1012
import io.github.digitalsmile.annotation.structure.Struct;

annotation-processor-test/src/test/java/io/github/digitalsmile/gpio/libvlc/LibVLC.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,9 @@
1616
@NativeMemory(headers = "libvlc/vlc/include/vlc/vlc.h")
1717
@NativeMemoryOptions(
1818
includes = "libvlc/vlc/include",
19-
systemIncludes = "/usr/lib/gcc/x86_64-linux-gnu/12/include/")
19+
systemIncludes = "/usr/lib/gcc/x86_64-linux-gnu/12/include/",
20+
debugMode = true
21+
)
2022
@Structs
2123
@Enums
2224
@Unions
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package io.github.digitalsmile.gpio.smbus;
2+
3+
import io.github.digitalsmile.annotation.NativeMemory;
4+
import io.github.digitalsmile.annotation.NativeMemoryOptions;
5+
import io.github.digitalsmile.annotation.structure.Struct;
6+
import io.github.digitalsmile.annotation.structure.Structs;
7+
8+
//@NativeMemory(headers = "/home/ds/linux/include/uapi/linux/i2c-dev.h")
9+
@NativeMemoryOptions(
10+
includes = "/home/ds/linux/include/",
11+
systemIncludes = {"/home/ds/linux/include/"})
12+
@Structs({
13+
@Struct(name = "i2c_smbus_ioctl_data", javaName = "SMBusIoctlData")
14+
})
15+
public interface SMBus {
16+
}

annotation-processor/src/main/java/io/github/digitalsmile/NativeProcessor.java

Lines changed: 77 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,13 @@
44
import io.avaje.prism.GeneratePrism;
55
import io.github.digitalsmile.annotation.ArenaType;
66
import io.github.digitalsmile.annotation.NativeMemory;
7-
import io.github.digitalsmile.annotation.NativeMemoryException;
87
import io.github.digitalsmile.annotation.NativeMemoryOptions;
98
import io.github.digitalsmile.annotation.function.ByAddress;
109
import io.github.digitalsmile.annotation.function.NativeManualFunction;
1110
import io.github.digitalsmile.annotation.function.Returns;
12-
import io.github.digitalsmile.annotation.structure.Enums;
13-
import io.github.digitalsmile.annotation.structure.Structs;
14-
import io.github.digitalsmile.annotation.structure.Unions;
11+
import io.github.digitalsmile.annotation.library.NativeMemoryLibrary;
12+
import io.github.digitalsmile.annotation.structure.*;
13+
import io.github.digitalsmile.annotation.structure.Enum;
1514
import io.github.digitalsmile.annotation.types.interfaces.NativeMemoryLayout;
1615
import io.github.digitalsmile.composers.*;
1716
import io.github.digitalsmile.functions.FunctionNode;
@@ -25,6 +24,8 @@
2524
import io.github.digitalsmile.headers.mapping.OriginalType;
2625
import io.github.digitalsmile.headers.model.NativeMemoryNode;
2726
import io.github.digitalsmile.headers.model.NodeType;
27+
import io.github.digitalsmile.validation.NativeProcessorValidator;
28+
import io.github.digitalsmile.validation.ValidationException;
2829
import org.openjdk.jextract.Declaration;
2930
import org.openjdk.jextract.JextractTool;
3031
import org.openjdk.jextract.Position;
@@ -50,13 +51,18 @@
5051
import java.util.regex.Pattern;
5152
import java.util.stream.Stream;
5253

54+
import static java.util.Collections.emptyList;
55+
5356
@GeneratePrism(NativeManualFunction.class)
5457
@SupportedSourceVersion(SourceVersion.RELEASE_22)
5558
public class NativeProcessor extends AbstractProcessor {
5659

60+
private NativeProcessorValidator validator;
61+
62+
5763
@Override
5864
public Set<String> getSupportedAnnotationTypes() {
59-
return Set.of(NativeMemory.class.getName());
65+
return Set.of(NativeMemory.class.getName(), NativeMemoryLibrary.class.getName(), NativeManualFunction.class.getName());
6066
}
6167

6268

@@ -65,75 +71,105 @@ public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment
6571
if (roundEnv.processingOver()) {
6672
return true;
6773
}
68-
for (Element rootElement : roundEnv.getElementsAnnotatedWith(NativeMemory.class)) {
74+
this.validator = new NativeProcessorValidator(processingEnv.getMessager(), processingEnv.getElementUtils(), processingEnv.getTypeUtils());
75+
76+
for (Element rootElement : roundEnv.getRootElements()) {
77+
if (!rootElement.getKind().isInterface()) {
78+
continue;
79+
}
80+
var nativeMemory = rootElement.getAnnotation(NativeMemory.class);
81+
var manualFunctionElements = rootElement.getEnclosedElements().stream().filter(p -> p.getAnnotation(NativeManualFunction.class) != null).toList();
82+
var automaticFunctionElements = rootElement.getAnnotation(NativeMemoryLibrary.class);
83+
if (nativeMemory == null && manualFunctionElements.isEmpty() && automaticFunctionElements == null) {
84+
continue;
85+
}
86+
6987
try {
70-
var nativeAnnotation = rootElement.getAnnotation(NativeMemory.class);
71-
var nativeOptions = rootElement.getAnnotation(NativeMemoryOptions.class);
72-
var headerFiles = nativeAnnotation.headers();
88+
var parsed = Collections.<NativeMemoryNode>emptyList();
7389
var packageName = processingEnv.getElementUtils().getPackageOf(rootElement).getQualifiedName().toString();
90+
var nativeOptions = rootElement.getAnnotation(NativeMemoryOptions.class);
7491
if (nativeOptions != null && !nativeOptions.packageName().isEmpty()) {
75-
packageName = nativeOptions.packageName();
92+
packageName = validator.validatePackageName(nativeOptions.packageName());
7693
}
7794
PackageName.setDefaultPackageName(packageName);
7895

79-
var parsed = processHeaderFiles(rootElement, headerFiles, packageName, nativeOptions);
8096

81-
List<Element> functionElements = new ArrayList<Element>(roundEnv.getElementsAnnotatedWith(NativeManualFunction.class)).stream()
82-
.filter(f -> f.getEnclosingElement().equals(rootElement)).toList();
83-
processFunctions(rootElement, functionElements, packageName, parsed, nativeOptions);
97+
if (nativeMemory != null) {
98+
var headerFiles = nativeMemory.headers();
99+
parsed = processHeaderFiles(rootElement, headerFiles, packageName, nativeOptions);
100+
}
101+
102+
List<Element> manualFunctions = new ArrayList<>();
103+
for (Element manualFunction : manualFunctionElements) {
104+
validator.validateManualFunction(manualFunction);
105+
manualFunctions.add(manualFunction);
106+
}
107+
108+
if (automaticFunctionElements != null) {
109+
validator.validateAutomaticFunctions(parsed, automaticFunctionElements);
110+
}
84111

112+
processFunctions(rootElement, manualFunctions, packageName, parsed, nativeOptions);
113+
} catch (ValidationException e) {
114+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), e.getElement());
85115
} catch (Throwable e) {
86116
printStackTrace(e);
87-
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
117+
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage(), rootElement);
88118
}
89119
}
90120
return true;
91121
}
92122

93-
public record Type(String name, String javaName) {
94-
}
95-
96-
private List<NativeMemoryNode> processHeaderFiles(Element element, String[] headerFiles, String packageName, NativeMemoryOptions options) {
123+
private List<NativeMemoryNode> processHeaderFiles(Element element, String[] headerFiles, String packageName, NativeMemoryOptions options) throws ValidationException {
97124
if (headerFiles.length == 0) {
98-
return Collections.emptyList();
125+
return emptyList();
99126
}
100127
List<Path> headerPaths = getHeaderPaths(headerFiles);
101128
for (Path path : headerPaths) {
102129
if (!path.toFile().exists()) {
103130
processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Cannot find header file '" + path + "'! Please, check file location", element);
104-
return Collections.emptyList();
131+
return emptyList();
105132
}
106133
}
107134

108135
var structsAnnotation = element.getAnnotation(Structs.class);
109136
var unionsAnnotation = element.getAnnotation(Unions.class);
110137
var enumsAnnotation = element.getAnnotation(Enums.class);
111138

112-
List<Type> structs = null;
139+
List<String> structs = null;
113140
if (structsAnnotation != null) {
114-
structs = Arrays.stream(structsAnnotation.value()).map(struct -> new Type(struct.name(), struct.javaName())).toList();
115-
Arrays.stream(structsAnnotation.value()).forEach(struct -> PrettyName.addName(struct.name(), struct.javaName()));
141+
structs = Arrays.stream(structsAnnotation.value()).map(Struct::name).toList();
142+
for (Struct struct : structsAnnotation.value()) {
143+
var javaName = validator.validateJavaName(struct.javaName());
144+
PrettyName.addName(struct.name(), javaName);
145+
}
116146
}
117-
List<Type> enums = null;
147+
List<String> enums = null;
118148
if (enumsAnnotation != null) {
119-
enums = Arrays.stream(enumsAnnotation.value()).map(enoom -> new Type(enoom.name(), enoom.javaName())).toList();
120-
Arrays.stream(enumsAnnotation.value()).forEach(enoom -> PrettyName.addName(enoom.name(), enoom.javaName()));
149+
enums = Arrays.stream(enumsAnnotation.value()).map(Enum::name).toList();
150+
for (Enum enoom : enumsAnnotation.value()) {
151+
var javaName = validator.validateJavaName(enoom.javaName());
152+
PrettyName.addName(enoom.name(), javaName);
153+
}
121154
}
122-
List<Type> unions = null;
155+
List<String> unions = null;
123156
if (unionsAnnotation != null) {
124-
unions = Arrays.stream(unionsAnnotation.value()).map(union -> new Type(union.name(), union.javaName())).toList();
125-
Arrays.stream(unionsAnnotation.value()).forEach(union -> PrettyName.addName(union.name(), union.javaName()));
157+
unions = Arrays.stream(unionsAnnotation.value()).map(Union::name).toList();
158+
for (Union union : unionsAnnotation.value()) {
159+
var javaName = validator.validateJavaName(union.javaName());
160+
PrettyName.addName(union.name(), javaName);
161+
}
126162
}
127163

128164
var rootConstants = false;
129165
var debug = false;
130166
var systemHeader = false;
131-
List<String> includes = Collections.emptyList();
132-
List<String> systemIncludes = Collections.emptyList();
167+
List<String> includes = emptyList();
168+
List<String> systemIncludes = emptyList();
133169
if (options != null) {
134170
rootConstants = options.processRootConstants();
135171
includes = getHeaderPaths(options.includes()).stream().map(p -> "-I" + p.toFile().getAbsolutePath()).toList();
136-
systemIncludes = Arrays.stream(options.systemIncludes()).map(p -> "-isystem" + p).toList();
172+
systemIncludes = getHeaderPaths(options.systemIncludes()).stream().map(p -> "-isystem" + p.toFile().getAbsolutePath()).toList();
137173
debug = options.debugMode();
138174
systemHeader = options.systemHeader();
139175
}
@@ -149,7 +185,7 @@ private List<NativeMemoryNode> processHeaderFiles(Element element, String[] head
149185
printStackTrace(e);
150186
}
151187
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
152-
return Collections.emptyList();
188+
return emptyList();
153189
}
154190
}
155191
var parser = new Parser(packageName, processingEnv.getMessager(), processingEnv.getFiler());
@@ -167,11 +203,11 @@ private List<NativeMemoryNode> processHeaderFiles(Element element, String[] head
167203
}
168204
return parsed;
169205
} catch (Throwable e) {
170-
if (debug) {
171-
printStackTrace(e);
172-
}
206+
//if (debug) {
207+
printStackTrace(e);
208+
//}
173209
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, e.getMessage());
174-
return Collections.emptyList();
210+
return emptyList();
175211
}
176212
}
177213

@@ -191,9 +227,6 @@ private void processStructs(NativeMemoryNode node) {
191227
}
192228

193229
private void processEnums(NativeMemoryNode node, boolean rootConstants) {
194-
if (node.nodes().isEmpty()) {
195-
return;
196-
}
197230
var name = node.getName();
198231
if (node.getName().endsWith("_constants")) {
199232
if (!rootConstants) {
@@ -225,11 +258,6 @@ private void processFunctions(Element rootElement, List<Element> functionElement
225258
if (!(element instanceof ExecutableElement functionElement)) {
226259
continue;
227260
}
228-
var throwType = processingEnv.getElementUtils().getTypeElement(NativeMemoryException.class.getName()).asType();
229-
if (!functionElement.getThrownTypes().contains(throwType)) {
230-
processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, "Method '" + functionElement + "' must throw NativeMemoryException!", functionElement);
231-
break;
232-
}
233261
var instance = NativeManualFunctionPrism.getInstanceOn(functionElement);
234262
if (instance == null) {
235263
break;
@@ -240,7 +268,7 @@ private void processFunctions(Element rootElement, List<Element> functionElement
240268
break;
241269
}
242270
List<ParameterNode> parameters = new ArrayList<>();
243-
var returnType = OriginalType.of(processingEnv.getTypeUtils().erasure(functionElement.getReturnType()));
271+
var returnType = OriginalType.of(functionElement.getReturnType());
244272
var returnNode = flatten.stream().filter(p -> PrettyName.getObjectName(p.getName()).equals(returnType.typeName())).findFirst().orElse(null);
245273
if (returnNode == null) {
246274
var type = functionElement.getReturnType();
@@ -319,7 +347,7 @@ private OriginalType getBoundsOriginalType(ExecutableElement functionElement) {
319347

320348
private FileObject tmpFile;
321349

322-
private List<Path> getHeaderPaths(String... headerFiles) {
350+
private List<Path> getHeaderPaths(String... headerFiles) throws ValidationException {
323351
List<Path> paths = new ArrayList<>();
324352
for (String headerFile : headerFiles) {
325353
var beginVariable = headerFile.indexOf("${");
@@ -334,6 +362,7 @@ private List<Path> getHeaderPaths(String... headerFiles) {
334362
continue;
335363
}
336364
}
365+
headerFile = validator.validatePath(headerFile);
337366
Path headerPath;
338367
if (headerFile.startsWith("/")) {
339368
headerPath = Path.of(headerFile);

annotation-processor/src/main/java/io/github/digitalsmile/composers/FunctionComposer.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import javax.annotation.processing.Messager;
1717
import javax.lang.model.element.Modifier;
1818
import javax.lang.model.element.TypeParameterElement;
19+
import javax.tools.Diagnostic;
1920
import java.lang.foreign.MemorySegment;
2021
import java.lang.foreign.ValueLayout;
2122
import java.util.ArrayList;
@@ -156,9 +157,12 @@ public String compose(String packageName, String originalName, List<FunctionNode
156157
.returns(createTypeName(returnType))
157158
.addException(NativeMemoryException.class);
158159

160+
var returnTypeName = functionNode.returnNode().getType().typeName();
159161
for (TypeParameterElement typeParameterElement : functionNode.typeVariables()) {
160-
methodSpecBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement))
161-
.returns(TypeName.get(typeParameterElement.asType()));
162+
methodSpecBuilder.addTypeVariable(TypeVariableName.get(typeParameterElement));
163+
if (returnTypeName.equals(typeParameterElement.asType().toString())) {
164+
methodSpecBuilder.returns(TypeName.get(typeParameterElement.asType()));
165+
}
162166
}
163167

164168
for (ParameterNode parameterNode : functionNode.functionParameters()) {

annotation-processor/src/main/java/io/github/digitalsmile/headers/DeclarationParser.java

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
package io.github.digitalsmile.headers;
22

3-
import io.github.digitalsmile.NativeProcessor;
43
import io.github.digitalsmile.PackageName;
54
import io.github.digitalsmile.headers.mapping.OriginalType;
65
import io.github.digitalsmile.headers.model.NativeMemoryNode;
@@ -16,9 +15,9 @@
1615
import java.util.stream.Stream;
1716

1817
public class DeclarationParser {
19-
private final List<NativeProcessor.Type> structs;
20-
private final List<NativeProcessor.Type> unions;
21-
private final List<NativeProcessor.Type> enums;
18+
private final List<String> structs;
19+
private final List<String> unions;
20+
private final List<String> enums;
2221
private final Messager messager;
2322

2423
private final boolean parseSystemHeaders;
@@ -27,7 +26,7 @@ public class DeclarationParser {
2726
private final boolean parseNoEnums;
2827
private final boolean parseNoUnions;
2928

30-
public DeclarationParser(Messager messager, List<NativeProcessor.Type> structs, List<NativeProcessor.Type> enums, List<NativeProcessor.Type> unions,
29+
public DeclarationParser(Messager messager, List<String> structs, List<String> enums, List<String> unions,
3130
boolean parseSystemHeaders) {
3231
this.messager = messager;
3332
this.parseSystemHeaders = parseSystemHeaders;
@@ -63,7 +62,7 @@ public void parseDeclarations(Declaration.Scoped parent, NativeMemoryNode parent
6362
}
6463

6564
private boolean skipParsing(String declarationName, Declaration.Scoped.Kind kind) {
66-
var contains = Stream.of(structs, enums, unions).flatMap(List::stream).filter(t -> t.name().equals(declarationName)).findFirst().orElse(null) != null;
65+
var contains = Stream.of(structs, enums, unions).flatMap(List::stream).filter(t -> t.equals(declarationName)).findFirst().orElse(null) != null;
6766
return switch (kind) {
6867
case STRUCT -> parseNoStructs || (!contains && !structs.isEmpty());
6968
case ENUM -> parseNoEnums || (!contains && !enums.isEmpty());
@@ -220,10 +219,15 @@ private NativeMemoryNode parseVariable(Declaration declaration, Type type, int l
220219
var node = new NativeMemoryNode(declaration.name(), nodeType, OriginalType.ofObject(typeName), nextLevel, source);
221220
if (nodeType.isAnonymous()) {
222221
parseRoot(typeDeclared.tree(), node);
222+
return node;
223223
} else if (notInScope(typeDeclared.tree())) {
224224
parseDeclarations(typeDeclared.tree(), node, false);
225+
return node;
226+
} else if (level > 1) {
227+
return node;
228+
} else {
229+
return null;
225230
}
226-
return node;
227231
}
228232
default -> printWarning(declaration, "unknown field type " + type);
229233
}

0 commit comments

Comments
 (0)