How does Dependency Inversion works in such case? - oop

Good morning,
I have been reading a book from Robert Martin about software architecture and SOLID principles in particular, and I cannot get how does dependency inversion work.
Simple examples, yes, they are clear, like https://www.oodesign.com/dependency-inversion-principle.html
But when we have a plugin system where the parent must know about all plugins and load them, then what to do to avoid dependency of the parent from its plugins?
Example:
/*
// In plugins file below
class PluginA { }
class PluginB { }
*/
import { PluginA, PluginB } from '../plugins';
class A {
private plugins: Plugin[] = [];
construct() {
plugins.push(new PluginA(), new PluginB());
}
}
Yes, we defined an interface for all plugins, but we cannot push them without dependency from them, right?
So how to apply the Dependency Inversion principle in this case?
How to use plugins without dependencies?

Instead if importing them, construct the plugins elsewhere and inject them into the constuctor for A
class A {
private plugins: Plugin[] = [];
construct(pluginsToUse) {
plugins = pluginsToUse;
}
}

Related

How do I add new type-safe accessors to a Gradle Project?

We have a plugin which defines additional properties and adds them as extension properties, like:
project.extra["copyright"] = "Copyright ..."
Then in the build scripts, I can access this like:
project.extra["copyright"]
I'd like to just write:
project.copyright
Some Gradle plugins seem to do something like this. I can access project.sourceSets or project.kotlin even though those certainly aren't in the Project interface.
Using an IDE, I can drill into those convenience methods, which then lands me in some autogenerated code, so I know it's being autogenerated somewhere, but I haven't been able to find any clues to how to get this to happen for our own plugin. The Gradle docs mention type-safe accessors which is ultimately what allowed me to phrase the question, but the docs don't say how to add new ones.
How do we get this treatment for our own plugin?
Creating DSL-like APIS is documented here: https://docs.gradle.org/current/userguide/implementing_gradle_plugins.html#modeling_dsl_like_apis.
It's pretty easy to do. Here's a quick guide, and some tips.
Define an extension.
import org.gradle.api.provider.Property
interface MyExtension {
val copyright: Property<String>
}
It looks pretty boring! What's important is that it's either be an abstract class, or an interface - this is so Gradle can create a new instance (see 'Managed types'), and this is where the Gradle magic begins.
Aside: I've used Property<String> instead of String, although both will work. I recommend using types compatible with Lazy Configuration.
Register the extension.
import org.gradle.api.*
abstract class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val myExtension: MyExtension =
project.extensions.create("myPlugin", MyExtension::class.java)
}
}
This is the magic part. Simply by registering the extension against the project, Gradle will make the extension available and automatically generate Kotlin-DSL convenience methods.
Apply the plugin
// build.gradle.kts
plugins {
id("my-plugin")
}
myPlugin {
copyright.set("blah blah 2022")
}
Just like that, Gradle will generate Kotlin DSL accessors. Here's one of them:
// Accessorsajp3oxzka99ro52ctxwv0petb.kt
/**
* Configures the [myPlugin][MyExtension] extension.
*/
fun org.gradle.api.Project.`myPlugin`(configure: Action<MyExtension>): Unit =
(this as org.gradle.api.plugins.ExtensionAware).extensions.configure("myPlugin", configure)
Use the extension values
Going back to the Plugin definition, lets say you want to register your own task:
// a demo task
abstract class MyTask : DefaultTask() {
#get:Input
abstract val copyright: Property<String>
#TaskAction
fun run() {
println("Copyright is ${copyright.get()}")
}
}
(Note that this task, like the extension, is a 'managed type').
Now the custom task can be registered, and a default value for copyright set.
abstract class MyPlugin : Plugin<Project> {
override fun apply(project: Project) {
val myExtension: MyExtension = project.extensions.create("myPlugin", MyExtension::class.java)
project.tasks.register("myCustomTask", MyTask::class.java) {
copyright.set(myExtension.copyright)
}
}
}
It's good that both MyExtension and MyTask use Property<String> - the actual value will be evaluated lazily, and only if required.
Now if I run ./gradlew :myCustomTask, I see:
> Task :myCustomTask
Copyright is blah blah 2022
Further reading
What if you want to have multiple copyrights? Then you can create a configuration container
What if MyExtension has lots of properties and you want to provide them all to MyTask? Then you can use #Nested inputs
The extension properties can have default values.

How to bind i18next-browser-languagedetector to Kotlin?

The Gradle project is set by the JS plugin:
plugins {
kotlin("js") version("1.6.10")
}
and uses the LEGACY compilation backend:
kotlin {
js(LEGACY) {
// ...
}
}
My goal is to use the following dependencies in Kotlin sources:
dependencies {
implementation(npm("i18next", "21.6.11"))
implementation(npm("react-i18next", "11.15.4"))
implementation(npm("i18next-browser-languagedetector", "6.1.3"))
}
It was pretty easy to describe JS-Kotlin bridging for the first two dependencies:
#JsModule("i18next")
#JsNonModule
external val i18next: I18n
external interface I18n {
fun use(module: dynamic): I18n
}
#JsModule("react-i18next")
#JsNonModule
external val reactI18next: ReactI18next
external interface ReactI18next {
val initReactI18next: dynamic
}
Unfortunately, the last one - i18next-browser-languagedetector - is driving me some nuts with its configuration. Something like this:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val LanguageDetector: dynamic
doesn't work - the actual LanguageDetector provided by the declaration above is {}, so i18next doesn't consume it in Kotlin code (the JS code throws You are passing a wrong module! Please check the object you are passing to i18next.use()):
i18next.use(LanguageDetector) // fails
Can anyone please help me with a declaration of a JS-Kotlin bridge for the LanguageDetector?
Well, by debugging a little bit I've managed to solve this JS-Kotlin bridging issue. The working solution is the following declaration:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external val i18nextBrowserLanguageDetector: I18nextBrowserLanguageDetector
external interface I18nextBrowserLanguageDetector {
#JsName("default")
val LanguageDetector: dynamic
}
Now it's possible to do first parts of the i18next initialization chain:
i18next
.use(i18nextBrowserLanguageDetector.LanguageDetector)
.use(reactI18next.initReactI18next)
// ...
Unfortunately, it's difficult to say that I'm getting any intuition behind it (maybe because of my huge blind spots in JS) - so any additional clarification or explanations would be helpful still.
My biggest concern is that LanguageDetector from the declaration above should be a class, but it seems like no way to use something else rather than dynamic property. When I try to lift up the #JsName("default") annotation to mark some class protocol with it, it doesn't compile:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
#JsName("default")
external class LanguageDetector
It's not possible to use a nested class inside of the interface as well in this case:
#JsModule("i18next-browser-languagedetector")
#JsNonModule
external interface I18nextBrowserLanguageDetector {
#JsName("default")
class LanguageDetector
}
So while it seems to be solved, it's super-frustrating still.

How to access `JavaToolchainSpec` from within custom Gradle task

According to "Toolchains for plugin authors" it should be possible to access the configured JavaToolchainSpec from within a custom task. I try to use this approach within a custom plugin which creates a task based on the presence of the JavaPlugin and queries the configured languageVersion property. Here is a minimal example.
build.gradle
plugins {
id 'application'
id 'com.example.myplugin'
}
...
java {
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}
MyPlugin.kt
class MyPlugin : Plugin<Project> {
override fun apply(target: Project) {
target.plugins.withType<JavaPlugin> {
target.tasks.create<MyTask>("mytask")
}
}
}
MyTask.kt
abstract class MyTask : DefaultTask() {
init {
val extension = project.extensions.getByType<JavaPluginExtension>();
val languageVersion = extension.toolchain.languageVersion.get();
...
}
}
Once Gradle creates MyTask and the languageVersion property is queried, the build fails with the following error.
Cannot query the value of property 'languageVersion' because it has no value available.
My guess is that I am accessing the extension too early and it has not set its values at this time. My question now is if there is a way to wire up the configured properties (ideally with lazy mechanisms) with the task.
apparently you have to configure the toolchain object in the java plugin extension to be able to use JavaToolchainService, using something like:
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
if you don't do that Gradle will defaults to the current JVM and the whole toolchain API will be unavailable (leaving you with the error you reported)

how to configure build.gradle.kts to fix error "Duplicate JVM class name generated from: package-fragment"

I'm trying to follow this tutorial https://dev.to/tagmg/step-by-step-guide-to-building-web-api-with-kotlin-and-dropwizard and am instead writing my gradle.build file in Kotlin's DSL and am finding there is no direct mapping from Groovy to Kotlin and I'm now getting this error when running ./gradlew run:
(4, 1): Duplicate JVM class name 'dropwizard/tut/AppKt' generated from: package-fragment dropwizard.tut, package-fragment dropwizard.tut
plugins {
// Apply the Kotlin JVM plugin to add support for Kotlin on the JVM.
id("org.jetbrains.kotlin.jvm").version("1.3.31")
// Apply the application plugin to add support for building a CLI application.
application
}
repositories {
// Use jcenter for resolving dependencies.
// You can declare any Maven/Ivy/file repository here.
mavenCentral()
jcenter()
}
dependencies {
// Use the Kotlin JDK 8 standard library.
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
// Use the Kotlin test library.
testImplementation("org.jetbrains.kotlin:kotlin-test")
// Use the Kotlin JUnit integration.
testImplementation("org.jetbrains.kotlin:kotlin-test-junit")
compile("io.dropwizard:dropwizard-core:1.3.14")
}
application {
// Define the main class for the application
mainClassName = "dropwizard.tut.AppKt"
}
tasks.withType<Jar> {
manifest {
attributes["Main-Class"] = application.mainClassName
}
from({
configurations.runtimeClasspath.get().filter { it.name.endsWith("jar") }.map { zipTree(it) }
})
}
tasks.named<JavaExec>("run") {
args("server", "config/local.yaml")
}
I cannot tell (yet) why this happens but to work around it add #file:JvmName("SomethingUnique") to your JVM file. Note that renaming the file will not help and lead to the same error. Only changing the output name will resolve it.
The JVM only knows how to load classes, so the Kotlin-to-JVM compiler generates classes to hold top-level val or fun declarations.
When you have two similarly named files
// src/commonMain/kotlin/com/example/Foo.kt
package com.example
val a = 1
and
// src/jvmMain/kotlin/com/example/Foo.kt
package com.example
val b = 2
the kotlin-to-JVM compiler generates
package com.example;
public class FooKt {
public static final int a = 1;
}
and
public com.example;
public class FooKt {
public static final int b = 2;
}
Obviously, these two files can't coexist in the same JVM ClassLoader, hence the error message.
Solutions involve:
As #Fleshgrinder noted, adding a file-level JvmName annotation to at least one to override the derived name, FooKt.
Renaming files to be different where possible.
Moving top-level val and fun declarations from those files into other files so Kotlin does not need to create the FooKt class.
Moving top-level val and fun declarations into objects or companion objects.

How to find all classes in a package using reflection in kotlin

Is it possible to find all kotlin classes in a given package?
I also need only annotated classes but it's not a big deal. Any suggestions ?
Kotlin on the JVM suffers the same issue as Java in this regard due to the implementation of class loaders.
Class loaders are not required to tell the VM which classes it can provide, instead they are just handed requests for classes, and have to return a class or throw an exception.
Source and more information: Can you find all classes in a package using reflection?
To summarize the linked thread, there are a number of solutions that allow you to inspect your current class path.
The Reflections library is pretty straight forward and has a lot of additional functionality like getting all subtypes of a class, get all types/members annotated with some annotation, optionally with annotation parameters matching, etc.
Guava has ClassPath, which returns ClassInfo POJO's - not enough for your use case, but useful to know as Guava is available almost everywhere.
Write your own by querying classloader resources and code sources. Would not suggest this route unless you absolutely cannot add library dependencies.
Here's an example of querying classloader resources, adapted from https://www.javaworld.com/article/2077477/java-tip-113--identify-subclasses-at-runtime.html
Requires Java 8 or higher.
// Call this function using something like:
// findClasses("com.mypackage.mysubpackage")
// Modified from https://www.javaworld.com/article/2077477/java-tip-113--identify-subclasses-at-runtime.html
fun findClasses(pckgname: String) {
// Translate the package name into an absolute path
var name = pckgname
if (!name.startsWith("/")) {
name = "/$name"
}
name = name.replace('.', '/')
// Get a File object for the package
val url: URL = Launcher::class.java.getResource(name)
val directory = File(url.getFile())
println("Finding classes:")
if (directory.exists()) {
// Get the list of the files contained in the package
directory.walk()
.filter { f -> f.isFile() && f.name.contains('$') == false && f.name.endsWith(".class") }
.forEach {
val fullyQualifiedClassName = pckgname +
it.canonicalPath.removePrefix(directory.canonicalPath)
.dropLast(6) // remove .class
.replace('/', '.')
try {
// Try to create an instance of the object
val o = Class.forName(fullyQualifiedClassName).getDeclaredConstructor().newInstance()
if (o is MyInterfaceOrClass) {
println(fullyQualifiedClassName)
// Optionally, make a function call here: o.myFunction()
}
} catch (cnfex: ClassNotFoundException) {
System.err.println(cnfex)
} catch (iex: InstantiationException) {
// We try to instantiate an interface
// or an object that does not have a
// default constructor
} catch (iaex: IllegalAccessException) {
// The class is not public
}
}
}
}