Skip to content

Commit

Permalink
[GAE-Java] Introduce @GrapeSkip to support generated method overloadi…
Browse files Browse the repository at this point in the history
…ng (#2144)

- Introduce Annotation GrapeSkip
- Use `Unused` class to support overloading of FFIFuncGen method.
  • Loading branch information
zhanglei1949 committed Oct 18, 2022
1 parent 77b4ec5 commit 3038b89
Show file tree
Hide file tree
Showing 9 changed files with 350 additions and 0 deletions.
47 changes: 47 additions & 0 deletions analytical_engine/java/grape-annotation/pom.xml
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>grape-jdk-parent</artifactId>
<groupId>com.alibaba.graphscope</groupId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>grape-annotation</artifactId>

<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
</properties>

<dependencies>
<dependency>
<groupId>com.squareup</groupId>
<artifactId>javapoet</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.fastffi</groupId>
<artifactId>annotation-processor</artifactId>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<!-- Disable annotation processing for ourselves -->
<compilerArgument>-proc:none</compilerArgument>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
@@ -0,0 +1,17 @@
package com.alibaba.graphscope.annotation;

import static java.lang.annotation.RetentionPolicy.RUNTIME;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

@Retention(value = RUNTIME)
@Target(ElementType.TYPE)
public @interface GrapeSkip {
String[] vertexDataTypes();

String[] edgeDataTypes();

String[] msgDataTypes();
}
@@ -0,0 +1,222 @@
package com.alibaba.graphscope.annotation;

import com.alibaba.fastffi.annotation.AnnotationProcessorUtils;
import com.alibaba.graphscope.utils.Unused;
import com.squareup.javapoet.CodeBlock;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterSpec;
import com.squareup.javapoet.TypeSpec;

import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

import javax.annotation.processing.AbstractProcessor;
import javax.annotation.processing.Filer;
import javax.annotation.processing.Messager;
import javax.annotation.processing.RoundEnvironment;
import javax.annotation.processing.SupportedAnnotationTypes;
import javax.annotation.processing.SupportedSourceVersion;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.PackageElement;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.TypeMirror;
import javax.tools.Diagnostic.Kind;

@SupportedAnnotationTypes("com.alibaba.graphscope.annotation.GrapeSkip")
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class GrapeSkipAnnotationProcessor extends AbstractProcessor {

public Messager messager;

private final Set<String> LEGAL_TYPES =
new HashSet<String>(Arrays.asList("Long", "Integer", "Double", "String", "Empty"));

/**
* {@inheritDoc}
*
* @param annotations
* @param roundEnv
*/
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
// if (roundEnv.processingOver()) {
// output();
// return false;
// }
messager = processingEnv.getMessager();
for (TypeElement annotation : annotations) {
for (Element e : roundEnv.getElementsAnnotatedWith(annotation)) {
if (e instanceof TypeElement) {
processAnnotation((TypeElement) e, annotation);
}
}
}
return false;
}

void processAnnotation(TypeElement typeElement, TypeElement annotation) {
messager.printMessage(
Kind.NOTE,
"visiting element "
+ typeElement.getSimpleName()
+ ", annotation "
+ annotation.getSimpleName(),
typeElement);
if (isSameType(annotation.asType(), GrapeSkip.class)) {
GrapeSkip[] grapeSkips = typeElement.getAnnotationsByType(GrapeSkip.class);
if (grapeSkips.length != 1) {
throw new IllegalStateException("GrapeSkip shall only appear once");
}
checkAndGenerate(typeElement, grapeSkips[0]);
return;
}
}

public String checkAndGenerate(TypeElement typeElement, GrapeSkip grapeSkip) {
String dstClassName = "UnusedImpl";
messager.printMessage(
Kind.NOTE, "Processing element " + typeElement.getSimpleName(), typeElement);
PackageElement packageElement = AnnotationProcessorUtils.getPackageElement(typeElement);
String packageName = packageElement.getQualifiedName().toString();
messager.printMessage(Kind.NOTE, "package name" + packageName, typeElement);

TypeSpec.Builder classBuilder =
TypeSpec.classBuilder(dstClassName)
.addModifiers(Modifier.PUBLIC)
.addSuperinterface(Unused.class);
// collect vd,ed,msg types
String[] vdTypes = grapeSkip.vertexDataTypes();
String[] edTypes = grapeSkip.edgeDataTypes();
String[] msgTypes = grapeSkip.msgDataTypes();
check(vdTypes);
check(edTypes);
check(msgTypes);

// enumerate all possible classes;
int numClasses = msgTypes.length * edTypes.length * vdTypes.length;
String[] classNames = new String[numClasses];
generateClassNames(numClasses, classNames, vdTypes, edTypes, msgTypes);
messager.printMessage(Kind.NOTE, "generating " + numClasses + " skipClasses", typeElement);

// filling class first;
fillInSkipClasses(classBuilder, classNames);
messager.printMessage(Kind.NOTE, "Finish filling skip classes", typeElement);
//
// add factory impl
addMethod(classBuilder, classNames, edTypes.length * msgTypes.length, msgTypes.length);
messager.printMessage(Kind.NOTE, "Finish add factory class", typeElement);

writeTypeSpec(packageName, classBuilder.build(), typeElement);
return dstClassName;
}

public void generateClassNames(
int numClasses,
String[] classNames,
String[] vdTypes,
String[] edTypes,
String[] msgTypes) {
int ind = 0;
for (String vdType : vdTypes) {
for (String edType : edTypes) {
for (String msgType : msgTypes) {
String cur = vdType + edType + msgType;
classNames[ind] = cur;
ind += 1;
}
}
}
}

public void fillInSkipClasses(TypeSpec.Builder builder, String[] classNames) {
for (String className : classNames) {
TypeSpec.Builder innerBuilder =
TypeSpec.classBuilder(className)
.addModifiers(Modifier.STATIC, Modifier.PUBLIC)
.addSuperinterface(Unused.class);
builder.addType(innerBuilder.build());
}
}

public void addMethod(
TypeSpec.Builder builder, String[] className, int edMultiplyMsg, int msgNum) {
builder.addField(Unused[].class, "skips", Modifier.STATIC, Modifier.PRIVATE);
CodeBlock.Builder staticCodeBuilder = CodeBlock.builder();
staticCodeBuilder.addStatement(
"skips = new com.alibaba.graphscope.utils.Unused[$L]", className.length);
for (int i = 0; i < className.length; ++i) {
staticCodeBuilder.addStatement("skips[$L] = new $L()", i, className[i]);
}
builder.addStaticBlock(staticCodeBuilder.build());

MethodSpec.Builder getUnused =
MethodSpec.methodBuilder("getUnused")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addParameter(ParameterSpec.builder(Class.class, "vd").build())
.addParameter(ParameterSpec.builder(Class.class, "ed").build())
.addParameter(ParameterSpec.builder(Class.class, "msg").build())
.addCode(
CodeBlock.builder()
.addStatement(
"int a ="
+ " com.alibaba.graphscope.utils.Unused.class2Int(vd)")
.addStatement(
"int b ="
+ " com.alibaba.graphscope.utils.Unused.class2Int(ed)")
.addStatement(
"int c ="
+ " com.alibaba.graphscope.utils.Unused.class2Int(msg)")
.addStatement(
"int ind = a * $L + b * $L + c",
edMultiplyMsg,
msgNum)
.addStatement("return skips[ind]")
.build())
.returns(Unused.class);
builder.addMethod(getUnused.build());
}

private void check(String[] types) {
if (types.length <= 0) {
throw new IllegalStateException("Empty types!");
}
for (String type : types) {
if (!LEGAL_TYPES.contains(type)) {
throw new IllegalStateException("Unrecognized type " + type);
}
}
}

private void writeTypeSpec(String packageName, TypeSpec typeSpec, TypeElement typeElement) {
JavaFile javaFile = JavaFile.builder(packageName, typeSpec).build();
messager.printMessage(Kind.NOTE, javaFile.toString(), typeElement);
try {
Filer filter = processingEnv.getFiler();
javaFile.writeTo(filter);
} catch (IOException e) {
throw new IllegalStateException(
"Cannot write Java file "
+ javaFile.typeSpec.name
+ ". Please clean the build first.",
e);
}
}

private boolean isSameType(TypeMirror typeMirror, Class<?> clazz) {
TypeMirror typeMirror2 = getTypeMirror(clazz.getName());
return processingEnv.getTypeUtils().isSameType(typeMirror, typeMirror2);
}

private TypeMirror getTypeMirror(String name) {
TypeElement typeElement = processingEnv.getElementUtils().getTypeElement(name);
if (typeElement == null) {
throw new IllegalStateException("Cannot get TypeElement for " + name);
}
return typeElement.asType();
}
}
@@ -0,0 +1,44 @@
package com.alibaba.graphscope.utils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
* A interface which will be subclassed by auto-generated grape skip classes, used to support
* overloading for ffi method.
*/
public interface Unused {

static Unused getUnused(Class<?> a, Class<?> b, Class<?> c) {
try {
Class<?> implClass = Class.forName("com.alibaba.graphscope.runtime.UnusedImpl");
Method method = implClass.getMethod("getUnused", Class.class, Class.class, Class.class);
if (method == null) {
throw new IllegalStateException("fail to get method");
}
return (Unused) method.invoke(null, a, b, c);
} catch (ClassNotFoundException
| NoSuchMethodException
| InvocationTargetException
| IllegalAccessException e) {
e.printStackTrace();
}
return null;
}

static int class2Int(Class clz) {
if (clz.equals(Long.class)) {
return 0;
} else if (clz.equals(Double.class)) {
return 1;
} else if (clz.equals(Integer.class)) {
return 2;
} else if (clz.equals(String.class)) {
return 3;
} else if (clz.getSimpleName().startsWith("EmptyType")) {
return 4;
} else {
throw new IllegalStateException("Not possible");
}
}
}
@@ -0,0 +1 @@
com.alibaba.graphscope.annotation.GrapeSkipAnnotationProcessor
4 changes: 4 additions & 0 deletions analytical_engine/java/grape-runtime/pom.xml
Expand Up @@ -56,6 +56,10 @@
<artifactId>grape-jdk</artifactId>
<classifier>shaded</classifier>
</dependency>
<dependency>
<groupId>com.alibaba.graphscope</groupId>
<artifactId>grape-annotation</artifactId>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
Expand Down
@@ -0,0 +1,9 @@
package com.alibaba.graphscope.runtime;

import com.alibaba.graphscope.annotation.GrapeSkip;

@GrapeSkip(
vertexDataTypes = {"Long", "Double", "Integer", "String", "Empty"},
edgeDataTypes = {"Long", "Double", "Integer", "String", "Empty"},
msgDataTypes = {"Long", "Double", "Integer", "String", "Empty"})
public class UnusedInvoker {}
6 changes: 6 additions & 0 deletions analytical_engine/java/pom.xml
Expand Up @@ -26,6 +26,7 @@
<name>Grape Java SDK</name>

<modules>
<module>grape-annotation</module>
<module>grape-jdk</module>
<module>grape-demo</module>
<module>grape-runtime</module>
Expand Down Expand Up @@ -63,6 +64,11 @@
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.alibaba.graphscope</groupId>
<artifactId>grape-annotation</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>com.alibaba.graphscope</groupId>
<artifactId>grape-jdk</artifactId>
Expand Down

0 comments on commit 3038b89

Please sign in to comment.