Gradle generate sources.jar for only public interfaces - java

I am working on a closed-source Android library (published as an AAR), and want to include some javadocs for consumers, which requires a sources.jar.
I know I could cherry-pick each file using an includes property or maybe even a whole package/folder.
task('androidSourcesJar', type: Jar) {
classifier = 'sources'
baseName = artifactBaseName
from android.sourceSets.main.java.srcDirs
include ('MyInterface1.kt', 'MyInterface2.kt', 'MyInterface3.kt')
}
Instead, is there a way to include only public classes, interfaces, methods, etc? This seems like a problem that would've come up before.

You could try adding something like this, instead of your include:
from 'src/main/java'
eachFile { currentFile ->
String contents = new File(currentFile.getSourcePath()).text
if(!contents.contains("public class")) {
currentFile.exclude()
}
}
I'm not entirely sure if that works, but it should set you on the right path to where you want to go.
Since Gradle does not actually do any code analysis, you can't just simply say "only include files that have classes that are public". Instead, you have to either write a custom plugin that will only include public classes, or do something like what I provided. It includes everything from the source directory, but runs a little bit of code on each file. First, it gets the contents of the file, then it checks if that file contains public class. If not, the file does't have a public class, and should be excluded.
Hope this helps! Feel free to ask any more questions if you have any.

Related

Is that possible implement the same code but only enabled when imported the jar

Is that possible implement the same code but only enabled when adding a dependency to SpringBoot project?
If possible, how to achieve it?
I want to implement the code like this:
DoSomethingUtil doSomethingUtil = new DoSomethingUtil();
doSomethingUtil.send("API URL", "System A", "Hello");
It would do nothing when project didn't add the implement of the DoSomethingUtil.java.
After adding to pom.xml that which would implement the DoSomethingUtil.java, it would really do something.
Given that you don't need to know about DoSomethingUtil anywhere else in your code, you can run something on it only if it's present in your classpath (without importing it) if you use reflection all the way:
try {
Class<?> dsuClass = Class.forName("do.something.util.DoSomethingUtil");
Object dsuInstance = dsyClass.getConstructor().newInstance();
Method sendMethod = dsuClass.getDecaredMethod("send", String.class, String.class, String.class);
sendMethod.invoke(dsuInstance, "API URL", "System A", "Hello");
} catch (Exception ignored) {}
You may want to revisit the poor error handling above to distinguish (at least) between class not being present in the classpath and send() method invocation failure.
What you appear to be describing is adding a dependency, not "importing" something.
Will it work?
Sort of. What you could do is overlay the definition of the.pkg.DoSomethingUtil with another version of the.pkg.DoSomethingUtil in a different JAR file. It can work, but it makes your application sensitive to the order of the JARs on the runtime classpath. That makes your application fragile ... to say the least.
You can probably make this work with classic Java if you have full control of the runtime classpath. However:
I'm not sure if it will work with SpringBoot.
If you tried this sort of thing on Android, the APK builder would protest. It treats the scenario of two classes with the same full name as an error.
I think there is a better solution:
Refactor the code so that there is a DoSomethingUtil interface and two classes; e.g. RealDoSomethingUtil and DummyDoSomethingUtil.
Replace new DoSomethingUtil() with a call to a factory method.
Implement the factory method something like this:
private static Class<?> doSomethingClass;
public static synchronized DoSomethingUtil makeDoSomethingUtil() {
if (doSomethingClass == null) {
try {
doSomethingClass = Class.forName("the.pkg.RealDoSomethingUtil");
} catch (Exception ex) {
doSomethingClass = the.pkg.DummyDoSomethingUtil.class;
}
}
return (DoSomethingUtil) (doSomethingClass.newInstance());
}
Put RealDoSomethingUtil into the add-on JAR file, and DoSomethingUtil, RealDoSomethingUtil and the factory method into the main JAR file.
You should probably make the exception handling more selective so that it deals with different classloader errors differently. For example, if RealDoSomethingUtil exists but can't be loaded, you probably should log that ... or maybe let the exception crash the application.
You could also make use of ServiceLoader, but I don't know if it would be simpler ...
The java Service Provide API (SPI) is there to detect wether implementation(s) of an interface exists.
You have a jar with an interface DoSomethingUtil in your application.
Possibly on the class path an implementation jar (MyDoSomethingUtilImpl implements DoSomethingUtil), with an entry in META-INF/services.
You must check whether the interface is implemented.
One could make a fallback implementation.

Getting the resource path of the java project from a custom gradle plugin

Trying to create a custom gradle plugin in java, how do i get the resources path from inside the task class?
public class MyCustomPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getTasks().register("doStuff", CustomTask.class);
}
}
public class CustomTask extends DefaultTask {
// How do I get java project resources dir from here?
#Inject
public CustomTask(ProjectLayout projectLayout) {
directoryProperty = projectLayout.getBuildDirectory();
}
#TaskAction
public void execute() {
...
}
}
I would recommend to not get the directory inside the task, because the plugin that provides it might not be applied. Instead I would do it from within your plugin that registers the task, this way you can also ensure that the necessary plugin is actually applied. Gradle will display an error if the task is used without a value being assigned to the input that explains that nothing was assigned.
With the kotlin-dsl:
#CacheableTask
abstract class CustomTask : DefaultTask() {
#get:InputFiles
abstract val resources: FileCollection
//...
}
I cannot answer if #InputFiles is the right annotation for your use case, because I don't know what you want to do with the resource. Refer to the Gradle documentation for more information on the available annotations, and what they do.
plugins {
java
}
tasks.register<CustomTask>("customTask") {
resources.set(sourceSets.main.map { it.resources })
}
Notice the map {} which ensures that our task has a dependency on the processResources task, this is done automatically for us because we stick to the provider API of Gradle for everything.
Note that the resources are by default in one directory, but they don't have to be. This is why the resources are defined as SourceDirectorySet and not as Provider<Directory>. The same is true for anything that originates from the SourceSetContainer. It is easier to explain with Java source code: imagine you have Java and Kotlin, then you will have src/main/java and src/main/kotlin, hence, 2 directories. The former will have a **/*.java include filter, whereas the latter has a **/*.kt includes filter. If we just want to get all sources then we use sourceSets.main.map { it.java.sourceDirectories }, and if we want to get one of both it gets complicated. 😝
First, you'd have to ensure this is a Java project: either applying the "java" plugin from your plugin (project.getPluginManager().apply("java")), or only registering the task when the "java" plugin has been applied by the user (project.getPluginManager().withPlugin("java", ignored -> { project.getTasks().register(…); });).
You could then get the resources from the main source set:
SourceSetContainer sourceSets = project.getExtensions().getByType(SourceSetContainer.class);
// Use named() instead of get() if you prefer/need to use providers
SourceSet mainSourceSet = sourceSets.get(SourceSet.MAIN_SOURCE_SET_NAME);
SourceDirectorySet resources = mainSourceSet.getResources();
BTW, the best practice is to have tasks only declare their inputs and outputs (e.g. I need a set of directories, or files, as inputs, and my outputs will be one single file, or in one single directory) and have the actual wiring with default values be done by the plugin.
You could have the plugin unconditionally register the task, then conditionally when the "java" plugin is applied configure its inputs to the project resources; or conditionally register the task or unconditionally apply the "java" plugin, as I showed above.
You can access the sources through the project.sourceSets.
#Inject
public CustomTask(Project project) {
directoryProperty = project.projectLayout.getBuildDirectory();
sourceSet = project.sourceSets.main
}
See also the reference documentation here: https://docs.gradle.org/current/userguide/java_plugin.html#sec:java_project_layout

Creating an 'Empty Shell' Jar

I'm looking on guidance on how I can essentially create an 'empty shell' jar with maven. The idea is I have a java project, and I want to export the my.project.api classes (with package) into its own jar without saving the methods / constructors actual code inside.
For example, lets say I have the following:
public class Test {
public void doSomething(String message) {
System.out.println(message);
}
}
I want to export a separate jar which would keep its package declaration, and export as:
public class Test {
public void doSomething(String message) {}
}
The reasoning for this is the project itself is exclusive, but I want to allow other developers to make their own integrations without the need of the physical product / project. This way by them hooking into say my.project.api.Test, they'd be able to see the methods and do as they wish.
Hopefully this clarifies enough, it would export as a separate jar maybe as 'MyProject-API.jar' or something.
Thanks!
This very much looks like a use case for interfaces.

Java ServiceLoader: no error, but also no implementations found

I am trying to figure out the ServiceLoader of Java.
I set up a VERY basic test-implementation:
public class BaseThingy {
public BaseThingy(){
Iterator<WriteService> iter = ServiceLoader.load(WriteService.class).iterator();
while (iter.hasNext()) {
WriteService plugin = iter.next();
System.out.print(plugin.getText());
}
}}
Interface:
public interface WriteService {
String getText();}
Now, as far as I understood things, I write an implementation, and put the implementing class (with no further files or manifest??) into a jar.
The project itself requires a file: META-INF\services\experimental.plugin.WriteService
In this file, I write the full name of the implementation (in my case. that would be experimental.plugin.WriteHello).
Now, I am working within Intellij as IDE.
Where should I put the file, and where should I put the jar with the implementing class?
I am not getting any errors, but neither is ANY implementation being found.
Or does the jar-file need anything additional after all?
The META-INF/services/experimental.plugin.WriteService must be in the classpath as well as the JAR with the implementation.

Java - accessing strings in non-compiled .java files during runtime

I've got a problem for you.
I've got a bunch of Java files (.java) sitting around and they all contain a class declaration and an array of strings. I need to do stuff with the array. What is the best way to access it?
I tried using JavaCompiler class, but that didn't seem to work - so should I use regex or something?
Here's a sample of what the files look like:
package com.mypack.costmgr;
public class Cost_en extends java.util.ListResourceBundle {
static final Object[][] contents = new String[][] {
{"ACTIVE", "ACTIVE"},
{"Joe", "asfag"},
{"lolcats", "cheezburger"},
{"HELP", "OH GOD NOT THE BEES"},
{"asdfffff", "hacks"}
};
public Object[][] getContents() {
return contents;
}
}
And there's probably a hundred of these files.
So, to summarize: what is the best way to gain access to that data?
(Obviously, I cannot simply compile them with my project.)
You have to compile the .java files and make them .class files. Then you put those .class files on your classpath. At this point you can now make a reference to the contents of each of those files. Since contents is static you can get a reference to it by doing the following:
class MyAwesomeClass
{
Object[][] myArray = Cost_en.contents;
}
Resource bundles
Spring Roo has an interesting Java language parser and manipulation framework for their plugins. It's used to extract info from user created .java files as part of the code supporting AspectJ. Maybe you can create a Roo plugin to handle what you're trying to do?

Categories