Jeta: How to create custom annotation processors - java

There is plenty of features that already available on Jeta, but what if something is missing. Can I create my own annotations and generate metacode for them?
Needed a step-by-step tutorial how to create custom Jeta processors.

How to create custom processors, step-by-step tutorial
Step 1: Hello, World project
For this tutorial let's create a simple Gradle project with one module app and with a single class SayHelloApp. This class writes Hello, World! to standard output.
For the illustration we are going to create Hello annotation that sets Hello, Jeta! string to the annotated fields.
Step 2: common module
First, we need a module that will be accessible in app and apt (will create shortly) modules. In common module we need two classes - Hello annotation and HelloMetacode interface:
Step 3: apt module
apt - is a module in which we'll create all the required for code generation classes. For this tutorial we need a processor that will handle our Hello annotation.
Note that this module depends on common module so we used Hello annotation as a parameter for the super constructor. By doing that we're saying to Jeta that we need all the elements annotated with given type. The module also depends on jeta-apt in order to get access to the Jeta classes.
Step 4: Processor
Created SayHelloProcessor now does nothing. Let's add some logic in it. The idea here is to generate java code that sets Hello, Jeta string to the fields annotated with Hello.
Note that Jeta uses JavaPoet to create java source code. It's really great framework by Square. Please, check it out on GitHub.
First, we need that our metacode implements HelloMetacode. To do that we'll add super interface to the builder:
MetacodeContext context = roundContext.metacodeContext();
ClassName masterClassName = ClassName.get(context.masterElement());
builder.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(HelloMetacode.class), masterClassName));
Next, implement HelloMetacode by creating void setHello(M master) method:
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("setHello")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(masterClassName, "master");
Finally, the statements for each element annotated with Hello, that Jeta passes in process method via roundContext parameter:
for (Element element : roundContext.elements()) {
String fieldName = element.getSimpleName().toString();
methodBuilder.addStatement("master.$L = \"Hello, Jeta\"", fieldName);
}
Here is the complete SayHelloProcessor listing:
package org.brooth.jeta.samples.apt;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.ParameterizedTypeName;
import com.squareup.javapoet.TypeSpec;
import org.brooth.jeta.apt.MetacodeContext;
import org.brooth.jeta.apt.RoundContext;
import org.brooth.jeta.apt.processors.AbstractProcessor;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
public class SayHelloProcessor extends AbstractProcessor {
public SayHelloProcessor() {
super(Hello.class);
}
#Override
public boolean process(TypeSpec.Builder builder, RoundContext roundContext) {
MetacodeContext context = roundContext.metacodeContext();
ClassName masterClassName = ClassName.get(context.masterElement());
builder.addSuperinterface(ParameterizedTypeName.get(
ClassName.get(HelloMetacode.class), masterClassName));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("setHello")
.addAnnotation(Override.class)
.addModifiers(Modifier.PUBLIC)
.returns(void.class)
.addParameter(masterClassName, "master");
for (Element element : roundContext.elements()) {
String fieldName = element.getSimpleName().toString();
methodBuilder.addStatement("master.$L = \"Hello, Jeta\"", fieldName);
}
builder.addMethod(methodBuilder.build());
return false;
}
}
Step 5: Metacode
All the required for code generating classes are created and we're ready to try. But first, we need to add jeta.properties file in order to configurate Jeta. You can find more details about this file on this page. The file should be located in the root package. For our tutorial its content would be:
metasitory.package=org.brooth.jeta.samples
processors.add=org.brooth.jeta.samples.apt.SayHelloProcessor
Next, modify SayHelloApp. Instead of initializing text field we'll put Hello annotation on it:
public class SayHelloApp {
#Hello
String text;
}
And build.gradle:
group 'org.brooth.jeta-samples'
version '1.0'
buildscript {
repositories {
maven {
url 'https://plugins.gradle.org/m2/'
}
}
dependencies {
classpath 'net.ltgt.gradle:gradle-apt-plugin:0.5'
}
}
apply plugin: 'net.ltgt.apt'
apply plugin: 'java'
sourceCompatibility = 1.7
repositories {
mavenCentral()
jcenter()
}
compileJava {
options.sourcepath = files('src/main/java')
}
dependencies {
apt project(':apt')
compile project(':common')
compile 'org.brooth.jeta:jeta:+'
}
Now we're ready to generate metacode. Run next command in your console:
./gradlew assemble
If there is no problems so far, we'll see SayHelloApp_Metacode file under app/build directory:
Step 6: Controller
Controllers are the classes that apply metacode to the masters. Let's create one for HelloMetacode in app module:
package org.brooth.jeta.samples;
import org.brooth.jeta.MasterController;
import org.brooth.jeta.metasitory.Metasitory;
public class SayHelloController<M> extends MasterController<M, HelloMetacode<M>> {
public SayHelloController(Metasitory metasitory, M master) {
super(metasitory, master, Hello.class, false);
}
public void setHello() {
for (HelloMetacode<M> metacode : metacodes)
metacode.setHello(master);
}
}
Step 7: MetaHelper
MetaHelper is a simple static-helper class. You shouldn't use it in your project if you are not comfortable with static helpers. You can read more details about this class on this page.
Anyway, let's create MetaHelper in app module:
package org.brooth.jeta.samples;
import org.brooth.jeta.metasitory.MapMetasitory;
import org.brooth.jeta.metasitory.Metasitory;
public class MetaHelper {
private static MetaHelper instance;
private final Metasitory metasitory;
public static MetaHelper getInstance() {
if (instance == null)
instance = new MetaHelper("org.brooth.jeta.samples");
return instance;
}
private MetaHelper(String metaPackage) {
metasitory = new MapMetasitory(metaPackage);
}
public static void setHello(Object master) {
new SayHelloController<>(getInstance().metasitory, master).setHello();
}
}
Note that we must pass to MapMetasitory the same package ("org.brooth.jeta.samples") that we specified as metasitory.package in jeta.properties.
Step 8: Usage
The last step - we invoke our MetaHelper's method. Here is the complete listing of SayHelloApp:
package org.brooth.jeta.samples;
public class SayHelloApp {
#Hello
String text;
public SayHelloApp() {
MetaHelper.setHello(this);
}
public void sayHello() {
System.out.print(text);
}
public static void main(String[] args) {
new SayHelloApp().sayHello();
}
}
Finally, we can run SayHelloApp. In the console we should see:
Hello, Jeta
Links
This tutorial on GitHub
Jeta Website
Jeta on Android
Happy code-generating! :)

Related

Trying to run a test for a java class in Spock framework but I get this weird error about the constructor

This is the error I get when I try to create the class under test
Could not find matching constructor for: com.pittacode.apihelper.json.JsonObjectFlattener()
groovy.lang.GroovyRuntimeException: Could not find matching constructor for: com.pittacode.apihelper.json.JsonObjectFlattener() at com.pittacode.apihelper.json.JsonObjectFlattenerTest.flatten json object with one nested object(JsonObjectFlattenerTest.groovy:12)
This is my test class
package com.pittacode.apihelper.json
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import spock.lang.Specification
class JsonObjectFlattenerTest extends Specification {
def classUnderTest = new JsonObjectFlattener()
def "flatten json object with one nested object"() {
given:
def jsonString = """
{
"1-1": 11,
"1-2": {
"2-1": "21"
},
"1-3": 13
}
"""
def jsonObject = JsonParser.parseString(json).getAsJsonObject()
when:
JsonObject result = classUnderTest.flatten(jsonObject)
then:
result.keySet().containsAll(["1-1", "1-2", "1-3", "2-1"])
}
}
I have a gradle project with one subproject and a module-info.java
plugins {
id "groovy"
id "application"
id "org.beryx.jlink" version "2.25.0"
id "org.javamodularity.moduleplugin" version "1.8.10"
}
repositories {
mavenCentral()
}
ext {
log4jVersion = "2.17.2"
}
dependencies {
implementation("com.jayway.jsonpath:json-path:2.7.0") {
exclude group: "com.fasterxml.jackson.core"
exclude group: "com.google.gson"
}
implementation("com.fasterxml.jackson.core:jackson-databind:2.13.2")
implementation("com.google.code.gson:gson:2.9.0")
implementation("org.apache.logging.log4j:log4j-api:${log4jVersion}")
runtimeOnly("org.apache.logging.log4j:log4j-core:${log4jVersion}")
annotationProcessor("org.apache.logging.log4j:log4j-core:${log4jVersion}")
runtimeOnly("org.apache.logging.log4j:log4j-slf4j-impl:${log4jVersion}")
testImplementation("org.codehaus.groovy:groovy:3.0.9")
testImplementation("org.spockframework:spock-core:2.0-groovy-3.0")
}
application {
mainClass = "com.pittacode.apihelper.Runner"
mainModule = "com.pittacode.apihelper"
}
tasks.named("test") {
useJUnitPlatform()
}
jlink {
forceMerge "log4j", "jackson"
// options = ["--bind-services"] // makes jre bigger but has everything, good way to test stuff
launcher {
name = "apihelper"
jvmArgs = ["-Dlog4j.configurationFile=./log4j2.xml", "-Dlog4j2.debug=false"]
}
jpackage {
if (org.gradle.internal.os.OperatingSystem.current().windows) {
installerOptions += ["--win-per-user-install", "--win-dir-chooser", "--win-menu", "--win-shortcut"]
imageOptions += ["--win-console"]
}
}
}
tasks.jlink.doLast {
copy {
from("src/main/resources")
into("$buildDir/image/bin")
}
}
This is the class
package com.pittacode.apihelper.json;
import com.google.gson.JsonObject;
public final class JsonObjectFlattener {
public JsonObjectFlattener() {
}
public JsonObject flatten(JsonObject o) {
return null;
}
}
The weird thing is that in another specification class if I try to initiate another object (unrelated) it seems to create it just fine. That one has parameters so I tried adding some in the flattener as well but didn't seem to make a difference
Well this was silly,
Like I mentioned I am using java modules and it seems that I need to export the packages that contain the classes I want to test.
module com.pittacode.apihelper {
requires jdk.crypto.ec; // needed for ssl communication
requires org.slf4j;
requires java.net.http;
requires java.sql;
requires com.google.gson;
requires json.path;
requires org.apache.logging.log4j;
requires com.fasterxml.jackson.databind;
exports com.pittacode.apihelper;
exports com.pittacode.apihelper.json; // <-- this is the missing line
}

JAVA ClassLoader.class and any other explicite class calling of "getResource(...)" work differently?

note: this is a named-module java project
Why does getting a resources in java work like this?
I got two packages, in main func both are printing the URL of "/respath/tmp.txt" in a jar-file syntax with corresponding to its classes.
How does this code really deffer to each other?
// mod-one
package com.pkg.one;
import ...
import com.pkg.two.ClassNameTwo;
class ClassNameOne {
... main()
print(ClassNameOne.class.getResource("/resPath/tmp.txt")); // works fine
print(ClassNameTwo.getCustomRes("resPath/tmp.txt", ClassNameOne.class); // it return null
}
note: this is a named-module java project
// mod-two
package com.pkg.two;
import ...
class ClassNameTwo {
/**
return "ClassLoader.getSystemResource(...)" if sourceClass is null.
*/
public static URL getCustomRes(String sourcePath, Class<?> sourceClass) {
...
URL url = null;
if (sourceClass == null)
url = ClassLoader.getSystemResource(sourcePath);
else
url = sourceClass.getResource("/" + sourcePath);
...
return url;
}
}
ok, I got an answer.Because this is a named-module java project you need to give access at to your "resouce-dir" to another module but why did it gives me a warning: "package is empty or does not exist", when it is not empty?
e.q:
module mod.one { // this module need to access the "res-dir" of mod.two
requires mod.two;
...
}
module mod.two {
...
opens my.path.to.res; // dir: my/path/to/res
}
And can we access/modify a third-party module res-dir that is not open?

Soap Webservice Client for JAVAFX Application

I am trying to call the webservice for my application. If I call it in a sample project it is working perfectly fine. But when I merge it with My Java FX it is giving me so many errors. Web Service Client is auto generated using the Eclipse. I am trying to call the Methods only. Can Anyone help me?
Error: **Correction** I have edited it and I am using now JAVASE-15 and JVAFX-SDK 11.0.2
The package javax.xml.namespace is accessible from more than one module: java.xml, jaxrpc
Correction Update 2: I have removed Java.xml dependencies and module-info file as well.
but the new error is this
**Error: Could not find or load main class gload.Main
Caused by: java.lang.NoClassDefFoundError: javafx/application/Application**
and IF I keep the module info file it shows:
**Error occurred during initialization of boot layer
java.lang.module.FindException: Module javafx.graphics not found, required by gload**
Model:
package gload.model;
import java.io.File;
import java.io.FileInputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import javax.swing.JOptionPane;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.text.PDFTextStripper;
import org.datacontract.schemas._2004._07.PE_PPER_MyPdmWebServiceClient_Data.CustomerItem;
import org.datacontract.schemas._2004._07.PE_PPER_MyPdmWebServiceClient_Data.Result;
import org.tempuri.IService;
import org.tempuri.ServiceLocator;
public class PdmData
{
public String scode;
public boolean state = false;
public static String CdfFile;
public static String pdflocation;
public static String Custom_Ci;
public static String Generic_Ci;
public static String Mp_ref;
public static String Interface;
public static String Comments;
public static String PersoAppli;
public static String Code;
public static String Revision;
public static String Customer_Name;
public static String Customer_reference;
public static String getCode() {
return Code;
}
public static void setCode(String code) {
Code = code;
}
public static String getRevision() {
return Revision;
}
public static void setRevision(String revision) {
Revision = revision;
}
public static String getCustomer_Name() {
return Customer_Name;
}
public static void setCustomer_Name(String customer_Name) {
Customer_Name = customer_Name;
}
public static String getCustomer_reference() {
return Customer_reference;
}
public static void setCustomer_reference(String customer_reference) {
Customer_reference = customer_reference;
}
public static String getPersoAppli() {
return PersoAppli;
}
public static void setPersoAppli(String persoAppli) {
PersoAppli = persoAppli;
}
public static String getGeneric_Ci() {
return Generic_Ci;
}
public static void setGeneric_Ci(String generic_Ci) {
Generic_Ci = generic_Ci;
}
public static String getCdfFile() {
return CdfFile;
}
public static void setCdfFile(String cdfFile) {
CdfFile = cdfFile;
}
public static String getPdflocation() {
return pdflocation;
}
public static void setPdflocation(String pdflocation) {
PdmData.pdflocation = pdflocation;
}
public String Cdffile(String reference) {
ServiceLocator locator = new ServiceLocator(); -------->Web Service Locator and call
try {
IService basicHttpBinding_IService = locator.getBasicHttpBinding_IService();
Result result = basicHttpBinding_IService.getFilebyDcode(reference);
//To download the files
String link = result.getLocation();
System.out.println(link);
File out = new File("C:\\TempDownload\\" + reference +".zip"); //Creating a zip file to store the contents of download file
new Thread(new Download(link,out)).start();
//To Unzip the file
Path source = Paths.get("C:\\TempDownload\\" + reference +".zip");
Path target = Paths.get("C:\\TempDownload\\Unzip");
try {
unzipFolder(source, target);
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
//Creating a File object for directory
File directoryPath = new File("C:\\TempDownload\\Unzip\\Pre Ppc" + reference + "A_Released");
//List of all files and directories
String[] contents = directoryPath.list();
System.out.println("List of files and directories in the specified directory:");
FilenameFilter pdffilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
String lowercaseName = name.toLowerCase();
if (lowercaseName.endsWith(".pdf")) {
return true;
} else {
return false;
}
}
};
String[] contents1 = directoryPath.list(pdffilter);
for(String fileName : contents1) {
System.out.println(fileName);
setCdfFile(fileName);
setPdflocation(directoryPath.toString());
}
//To extract the Data From PDF
File file = new File(getPdflocation() + "\\" + getCdfFile());
//FileInputStream fis = new FileInputStream(file);
PDDocument document = PDDocument.load(file);
PDFTextStripper pdfReader = new PDFTextStripper();
String docText = pdfReader.getText(document);
System.out.println(docText);
document.close();
//To extract details from document
String CI_Ref = "CI Ref";
int pos ;
pos = docText.indexOf(CI_Ref);
setGeneric_Ci(docText.substring(pos+7 , pos+15));
System.out.println("Generic CI: " + getGeneric_Ci());
//To get Details of CI
CustomerItem customerItem = basicHttpBinding_IService.getCiDetails(getGeneric_Ci());
setPersoAppli(customerItem.getPersoAppli());
setCode(customerItem.getCode());
setRevision(customerItem.getRevision());
setCustomer_Name(customerItem.getCustomerName());
setCustomer_reference(customerItem.getCustomerReference());
}catch (Exception e) {
e.printStackTrace();
JOptionPane.showMessageDialog(null, "Unable to reach Service : " + e.getMessage());
}
return getPersoAppli();
}
Module info file
module gload {
requires javafx.controls;
requires javafx.fxml;
requires java.desktop;
requires java.rmi;
requires java.base;
requires axis;
requires jaxrpc;
requires org.apache.pdfbox;
opens gload;
opens gload.views.main;
opens gload.utils;
opens gload.model;
opens gload.controllers;
opens org.tempuri;
opens org.datacontract.schemas._2004._07.PE_PPER_MyPdmWebServiceClient_Data;
}
and IF I keep Jaxrpc in classpath instead of module path I get error like this Description
The type javax.xml.rpc.ServiceException cannot be resolved. It is indirectly referenced from required .class files
OK, this won't really be an answer, more pointers to related issues and potential approaches to come up with solutions. But I'll post it as an answer as it is likely better to do that than lots of comments.
Unfortunately, you have multiple errors and issues, so I'll try to deal with some of them seperately.
According to:
Java FX Modular Application, Module not found (Java 11, Intellij)
The error:
Error occurred during initialization of boot layer
java.lang.module.FindException:
Module X not found, required by Y
can occur when --module-path is wrong and the module can't be found. Probably, that is at least one of your issues. The linked answer is for Idea and I don't use Eclipse, so I don't know how to resolve the issue in Eclipse, but perhaps you could do some research to find out.
Regarding:
The package javax.xml.namespace is accessible from more than one module
there is some info on what is going on here:
Eclipse is confused by imports ("accessible from more than one module").
This fix appears tricky to me. Please review the linked questions and solutions. It looks like either you need to either
Forego Java 9+ modularity OR
Manage your dependencies to not include the violating transitive dependency OR
Change to a library that doesn't rely on the broken library (probably the preferred solution in this case).
The broken library causing this issue is likely the version of jaxrpc you are using. My guess is that some of the relevant XML libraries were only added to standard Java in Java 9, but the jaxrpc library you are using was developed prior to that. So, jaxrpc either includes the XML libraries in its classes or makes use of a transitive library that does the same. This causes a conflict because the XML libraries can only be included once in the project.
Further info on your issues is in this answer:
Eclipse can't find XML related classes after switching build path to JDK 10
The info is so ugly . . . you could read the answer, it may either help or discourage you.
Some things you could do to help resolve the situation
What should be done about this is kind of tricky and will depend on your skill level and how or if you can solve it. I'll offer up some advice on some things you could do, but there are other options. You know your application better than I so you may be able to come up with better solutions for your application.
I'd advise separating these things out, just as a way of troubleshooting, get a project which works with all of the JavaFX components and one which works with all of the SOAP components and make sure they build and do what you want. Then try to combine the two projects either by integrating them into one project or running them in separate VMs with communication between the two (e.g. via an added REST API, though that is a much more complicated solution, so think hard about that before attempting it).
Also, upgrade to the latest version of JavaFX. I don't think it will fix your issue, but it can't hurt and it is possible some refinements in recent JavaFX versions may have done some things which might help ease some of your issues (though not all of them, as some of your issues stem from jaxrpc usage in a modular project, which is unrelated to JavaFX).
Also, and probably more importantly, consider using a different SOAP client framework that interacts better with modular Java 9+ than the broken implementation that jaxrpc appears to have.
In terms of whether you should make your application modular or not (include a module-info or not), I don't really know the best approach for you. Certainly, whichever way you choose you will run into issues. But, the issues and how to resolve them will be different depending on the chosen solution path (as I guess you have already discovered during the course of your investigation for the question).
If necessary, isolate the issues down to single separate issues. If you need help in resolving each separate issue post new questions that feature minimal reproducible example code to replicate the issue. Mind if you do so, that the code is absolutely minimal and also complete so that it replicates and asks about only one issue, not a combination of more than one and that the questions are appropriate tagged - e.g. if the question is about jaxrpc and modularity it should include jaxrpc and modular tags and no JavaFX code or tags (and vice versa) and certainly on pdf code or dependencies anywhere if that isn't part of the problem.

(Android Unity plugin) i created a simple plugin, expected 123, got 0

I'm trying to make a plugin for Unity, but not even the simplest class works.
In Android Studio I created a library module, and in it, the following class:
package com.vuforia.android.pluginlib;
import static android.os.Looper.getMainLooper;
public class Multi {
static public Multi mult=new Multi();
static public int testes =123;
}
After that I added to the gradle of lib, the following configurations of a Task,to create the aar:
task copyPlugin (type : Copy){
dependsOn assemble
from ('build/outputs/aar')
into ('../../Assets/Plugins/Android')
include(project.name+'-release.aar')
}
In unity, I created some sprite, and added such script to it:
using UnityEngine;
public class movetest : MonoBehaviour
{
private AndroidJavaClass javaClass = null;
void Update()
{
javaClass = new AndroidJavaClass("com.vuforia.android.pluginlib.Multi");
int i = javaClass.GetStatic<int>("testes");
Debug.Log("->>"+i);
}
}
and when clicking on run, then what is received "- >>0".
I assume you are running in the editor because you stated "when clicking on run"..
Java plugins will not work in the editor. You would need to deploy to an Android device to test the functionality.

org.openide.util.Lookup Cannot Find Any Classes Implementing

SQLUtils.java:
import org.openide.util.Lookup;
import java.util.ServiceLoader; // This doesn't work either
public class SQLUtils {
public static DBDriver getDriver(String prefix) {
for(DBDriver e : Lookup.getDefault().lookupAll(DBDriver.class)) {
System.out.println(e.getPrefix());
if(e.getPrefix().equalsIgnoreCase(prefix)) {
return e;
}
}
return null;
}
}
MySQLDriver.java:
public class MySQLDriver implements DBDriver {
#Override
public String getPrefix() {
return "mysql";
}
}
DBDriver.java:
import java.io.Serializable;
public interface DBDriver extends Serializable {
public String getPrefix();
}
Main.java:
public class Main {
public static void main(String[] args) {
DBDriver d = SQLUtils.getDriver("mysql");
}
}
This does nothing when running it, it cannot find any classes implementing.
What the program is trying to do is get the driver that is entered as a parameter for SQLUtils.getDriver(String prefix) (in Main.java).
For some reason I cannot get this to work.
I'm not familiar with OpenIDE Lookup mechanism, but I am familiar with the Java ServiceLoader mechanism.
You need to provide a file in the META-INF/services/ folder describing what classes implement specific interfaces. From the Java Docs describing the ServiceLoader class is this example:
If com.example.impl.StandardCodecs is an implementation of the
com.example.CodecSet service then its jar file also contains a file
named
META-INF/services/com.example.CodecSet
This file contains the single line:
com.example.impl.StandardCodecs # Standard codecs implementing com.example.CodecSet
What you are missing is a similar file that needs to be included on your classpath or within your JAR file.
You don't include you package names so I cannot provide a more direct example to help solve your problem.
I dropped the NetBeans API and switched to Reflections. I implemented Maven and ran it with IntelliJ. Works well for me.

Categories