Not sure if this is a VSCode Java extension issue or not. We're trying to have conditional dependencies included depending on a specific Spring Boot profile we're passing in as a command line argument. It seems that when conditions are statically analysable, the Java language server is able to correctly provide intellisense, but when they are not, it fails. Here's an example, where when we use true/false, intellisense works, but when we use the commented out lines, it doesn't. I imagine this is the expected behaviour, and it still compiles fine, I'm just wondering if there's a way around making it work.
def profiles = (project.hasProperty('profiles')
? project.property('profiles')
: "local,https").split(",")
dependencies {
// if (profiles.contains("local") && !profiles.contains("tc")) {
if (false) {
runtimeOnly "com.h2database:h2"
}
else {
implementation "org.postgresql:postgresql:$DB_DRIVER_VERSION"
// if (profiles.contains("tc")) {
if (true) {
implementation "org.testcontainers:postgresql:$TESTCONTAINERS_VERSION"
runtimeOnly "org.testcontainers:testcontainers:$TESTCONTAINERS_VERSION"
testCompile "org.testcontainers:junit-jupiter:$TESTCONTAINERS_VERSION"
}
}
}
Here's an example of what happens in a test file
// this import fails with intellisense, since the dependency is
// not included when using conditional dependencies based on CLI arguments
import org.testcontainers.junit.jupiter.Testcontainers;
#Profile("tc")
// #Testcontainers
public class SampleIntegrationTest {
I should reiterate though, that this isn't a problem during normal build/run, it's just intellisense that doesn't work very well.
Related
I'm trying to deal with annotation processors. I followed tutorials. Here is my code:
#ExampleAnnotation
private void someMethod(){
System.out.println("hi");
}
#Retention(RetentionPolicy.SOURCE)
#Target(ElementType.METHOD)
public #interface ExampleAnnotation {
}
#SupportedAnnotationTypes("org.example.ExampleAnnotation")
public class Processor extends AbstractProcessor {
#Override
public boolean process(Set<? extends TypeElement> anots, RoundEnvironment roundEnvironment) {
anots.forEach(System.out::println);
return true;
}
}
I created META-INF/SERVICES/javax.annotation.processing.Processor
and registered my processor: org.example.Processor. It seems like everything is OK, but block of code in the processor just dont start. I have no idea what is wrong. P.S.: I use Gradle and Java 11.
i fixed my issue and decided to make a little step-by-step tutorial to create simple JAP:
Java-Annotation-Processor-guide
This is guide for annotation processing using gradle
I spent 3 days trying to deal with it, but with this little tutorial you will made JAP in 5 minutes.
sorry for bad english :)
So, first you should do is create gradle subproject for your project:
create project itself with gradle init or intellij
add to your main project's settings.gradle file this line include "*your subproject name, lets say:*annotation-processor"
Congrats, now let go to the processor itself
here is so much tutorials on the web about this part, so you can read something like this https://www.baeldung.com/java-annotation-processing-builde,
or this (Если ты русский): https://habr.com/ru/company/e-legion/blog/206208/
Final step is very easy - you will add your annotation processor to main project.
!!! instead of annotation-processor you should use your subproject name !!!
kotlin (or java + kotlin) -> {
add this plugin to your plugins: id "org.jetbrains.kotlin.kapt" version "1.7.21"
add this to your dependencies:
kapt project('annotation-processor')
compileOnly project('annotation-processor')
}
java -> {
add this to your dependencies:
annotationProcessor project('annotation-processor')
compileOnly project('annotation-processor')
}
(or you can read this on github: https://github.com/Blu3cr0ss/Java-Annotation-Processor-guide)
Im having trouble resolving the following runtime error: "Multiple HTTP implementations were found on the classpath. To avoid non-deterministic loading implementations, please explicitly provide an HTTP client via the client builders, set the software.amazon.awssdk.http.service.impl system property with the FQCN of the HTTP service to use as the default, or remove all but one HTTP implementation from the classpath"
I have the following two dependencies in my gradle.build :
implementation 'software.amazon.lambda:powertools-parameters:1.12.3'
implementation 'software.amazon.awssdk:sns:2.15.0'
They both seem to use the default HTTP client and compiler cannot determine which one to use. See below the declaration and use of them in code:
private static SsmClient = SsmClient.builder().region(Region.of((region == null) ? Regions.US_EAST_1.getName() : region)).build();
private static SSMProvider ssmProvider = ParamManager.getSsmProvider(client);
static SnsClient sns = SnsClient.builder().credentialsProvider(DefaultCredentialsProvider.builder().build())
.region((region == null) ? Region.US_EAST_1 : Region.of(region)).build();
I cannot remove one from the class path since I need both for my application and I have not succesfully been able to define an awssdk client via the builders.
I tried this but still got same runtime error:
client = SsmClient.builder().httpClientBuilder(new SdkHttpClient() {
#Override
public void close() {
}
#Override
public ExecutableHttpRequest prepareRequest(HttpExecuteRequest request) {
return null;
}
})
Looks like you will have to exclude one of the versions of http-client in your pom
implementation('<dependency>') {
exclude group: '<group>', module: '<module>'
}
where dependency is one of:
'software.amazon.lambda:powertools-parameters:1.12.3'
'software.amazon.awssdk:sns:2.15.0'
You can run gradle dependencies to see the dependency tree to figure out which one you want to exclude from
According to the Gradle Documentation you can include a dependency as "API" meaning it will be passed on to the application implementing it's class path. Is there a way to pass only an individual class/exception instead of the entire dependency?
Example
In Library A (written by me) I am using Open SAML Core.
The Class CoolClass in the library throws Exception B (from Open SAML Core).
The build.gradle file includes:
dependencies {
implementation group: 'org.opensaml', name: 'opensaml-core', version: "24.0.2"
In Java Application C, I am implementing Library A.
If I use CoolClass in my application, then gradle build gives me the following error:
/ApplicationC/src/main/java/com/sorry/CoolClass.java:10: error: cannot access ExceptionB
.someMethod();
^
class file for org.opensaml.core.xml.io.ExceptionB not found
Is there a way to pass just that exception rather than including the entire library in the class path of my application?
Not really, but your plan ('include just this one thing') is the wrong solution.
The general principle you've run into is 'transitive dependencies'.
Project A is dependent on project B. However, B in turn depends on C... so does A depend on C now?
transitive dependencies come in two 'flavours': Exported and non-exported.
Project B is a whole bunch of code, but crucially, some of that code is 'visible'. Specifically, the public signatures.
If project B's API (which A can see) includes, say, public List<String> getNames(), that means that the List dependency is now something A also needs to have, otherwise it can't interact with this method at all. Fortunately, List is java.util.List which everybody always haves so that wouldn't be a problem, but imagine it's a type from something not in the java.* space. To make this work, you export. And, it means that anytime you use any type from one of your dependencies in one of your public signatures, you have now forced that dependency to be exported.
That's what you've done here, by declaring throws SomethingSpecificFromOpenSAML, you must now export openSAML.
Solution: Just.. don't throw that
If that's the one and only thing you use in a public signature, the answer seems very obvious. Stop throwing that, your code is just wrong. You should not declare in your throws statement anything that's an implementation detail, and if you have no intention of 'exporting' OpenSAML here, then clearly it's an implementation detail.
Instead, catch it and throw it as something else. Or, keep it as is, but declare to throw Exception:
// Example 1
public void saveGame() throws OpenSamlException {
doSamlStuffHere();
}
// Example 2
public void saveGame() throws Exception {
doSamlStuffHere();
}
// Example 3
public class SaveException extends Exception {
public SaveException(String msg, Throwable cause) {
super(msg, cause);
}
}
public void saveGame() throws SaveException {
try {
doSamlStuffHere();
} catch (OpenSamlException e) {
throw new SaveException("Saving game " + getSaveName() + " failed", e);
}
}
Example 1 is what you have now and it exports the idea of OpenSaml conceptually by explicitly naming it in the 'visible' part of the method (which is just its signature).
Example 2 fixes that by naming java.lang.Exception. It also takes away the ability to meaningfully interact with the problem as thrown (catch (Exception e) catches rather a lot), so it's a bit ugly. But it solves the problem.
Example 3 is 'correct': It fully hides SAML as an implementation detail (nothing publicly visible shows anything about SAML at all, just like Example 2), but it does let you interact specifically with the problem, by rethrowing it as an exception that is still specific. I'm using 'save a game' here just as example.
Example 2 and 3 mean you no longer need to export your transitive dependency.
Transitive dependencies do need to be on the classpath
Exported transitive dependencies need to be available as you compile (and write) the code. And, of course, that dep needs to be on the classpath when you run the code.
Non-exported transitive dependencies don't need to exist at all when you compile/write. But they still need to be on the classpath when you run the code, obviously. After all, you need SAML to exist in order to run your saveGame() method. You can't wish that away.
Let's say we have the following test code:
import org.jetbrains.annotations.NotNull;
import org.junit.Test;
public class NullTest {
#Test
public void testNull() {
doNothing(null);
}
private #NotNull String doNothing(#NotNull String value) {
return value;
}
}
The test will pass when running gradle test directly or if IDEA delegates the action to gradle.
But it will fail with IllegalArgumentException: Argument for #NotNull parameter 'value' must not be null exception if it runs using IDEA runner (not delegated to gradle).
The question is: how to fail the test running it with gradle?
The easiest solution I have found is to apply org.jetbrains.intellij plugin.
Because among other things this plugin "patches compile tasks to instrument code with nullability assertions".
apply plugin: 'org.jetbrains.intellij'
intellij {
instrumentCode = true
downloadSources = false
}
Try adding the following to your dependencies. It worked for me.
compile 'org.jetbrains:annotations:13.0'
With this code - no way, because you use annotations from org.jetbrains.annotations.*, that use only in intellij idea tests runner. For gradle, annotation #NotNull (or #Nullable) says nothing. Maven also doesn't see this annotation. I can advise you use Objects.requireNonNull(T obj) for null checking.
We found that Lombok's #NonNull works better. But you need to configure IDEA to prefer this one during nullability-related analysis and generation
Adding the following dependency worked for me:
compile group: 'org.jetbrains', name: 'annotations', version: '15.0'
Run the 'dependencies' task & push the refresh button in Gradle.
I want to write custom Lombok Annotation handlers. I know http://notatube.blogspot.de/2010/12/project-lombok-creating-custom.html. But the current lombok jar file does not contain many .class files, but files named .SCL.lombok instead.
I found, the .SCL.lombok files are the .class files, the build script of Lombok does rename them while generating the jar file, and the ShadowClassLoader is capable of loading these classes -- and the acronym SCL seems to come from this. It seems the reason for this is just to "Avoid contaminating the namespace of any project using an SCL-based jar. Autocompleters in IDEs will NOT suggest anything other than actual public API."
I was only able to compile my custom handler by
unpacking the contents of the lombok.jar
renaming the .SCL.lombok files to .class
adding the resulting directory to the compile classpath
In addition, to be able to use my custom handler, I needed to create a new fat jar containing both the lombok classes and my custom handler. The custom lombok class loader essentially prevents adding custom handlers in other multiple jars.
Is this the only way to extend Lombok? Or am I missing something?
I am using the following buildscript
apply plugin: 'java'
repositories {
jcenter()
}
configurations {
lombok
compileOnly
}
def unpackedAndRenamedLombokDir = file("$buildDir/lombok")
task unpackAndRenameLombok {
inputs.files configurations.lombok
outputs.dir unpackedAndRenamedLombokDir
doFirst {
mkdir unpackedAndRenamedLombokDir
delete unpackedAndRenamedLombokDir.listFiles()
}
doLast {
copy {
from zipTree(configurations.lombok.singleFile)
into unpackedAndRenamedLombokDir
rename "(.*)[.]SCL[.]lombok", '$1.class'
}
}
}
sourceSets {
main {
compileClasspath += configurations.compileOnly
output.dir(unpackedAndRenamedLombokDir, builtBy: unpackAndRenameLombok)
}
}
tasks.compileJava {
dependsOn unpackAndRenameLombok
}
dependencies {
compile files("${System.properties['java.home']}/../lib/tools.jar")
compile "org.eclipse.jdt:org.eclipse.jdt.core:3.10.0"
compile 'javax.inject:javax.inject:1'
lombok 'org.projectlombok:lombok:1.16.6'
compileOnly files(unpackedAndRenamedLombokDir)
}
In the meantime Reinier Zwitserloot created a new git-branch sclExpansionUpdate, that contains an updated version of the ShadowClassLoader:
ShadowClassLoader is now friendlier to trying to extend lombok.
Your (separate) jar/dir should have a file named
META-INF/ShadowClassLoader. This file should contain the string
'lombok'. If you have that, any classes in that jar/dir will be loaded
in the same space as lombok classes. You can also rename the class
files to .SCL.lombok to avoid other loaders from finding them.
I guess this did not yet make it into the main branch because it certainly has not been tested that much - I just tried it out for myself and it contains a little bug that prevents loading the required META-INF/services from extensions. To fix it you should replace two method calls to partOfShadow with inOwnBase:
[... line 443]
Enumeration<URL> sec = super.getResources(name);
while (sec.hasMoreElements()) {
URL item = sec.nextElement();
if (!inOwnBase(item, name)) vector.add(item); // <<-- HERE
}
if (altName != null) {
Enumeration<URL> tern = super.getResources(altName);
while (tern.hasMoreElements()) {
URL item = tern.nextElement();
if (!inOwnBase(item, altName)) vector.add(item); // <<-- AND HERE
}
}
I tested it with the above fix and it seems to work fine (not tested much though).
On a side note: with this new extension mechanism, it is now finally also possible to have the extensions annotation handlers and annotations in a different namespace than "lombok" - nice!
Using the input from this question and from the other answer (by Balder), we managed to put together a custom Lombok annotation handler: Symbok. Feel free to use that as a sample for writing your own.
BTW, instead of writing a custom Lombok handler, you could also implement a javac plugin instead -- it might be simpler.