Kotlin internal classes in Java visible publicly
Asked Answered
P

4

6

I am developing an Android crypto library in Kotlin. I have a couple of internal classes which become publicly visible in a Java app. Found this in documentations.

internal declarations become public in Java. Members of internal classes go through name mangling, to make it harder to accidentally use them from Java and to allow overloading for members with the same signature that don't see each other according to Kotlin rules;

Is there a way to get around this?

Plume answered 29/7, 2017 at 19:47 Comment(16)
you can down to use private visibility. the visibility of top-level types is package visibility in Java.Trenchant
To maintain the readability of code I can't make them private.Plume
well. there is no way to prevent the client code using your internal Kotlin classes. @PublishedApi only support for warnings, but the client code can forcing to call your internal classes.Trenchant
@Trenchant That's too bad. Probably will have to sacrifice modularity and make them privatePlume
so you should redesign your api. the problem is your design rather than the visibility. maybe many features can be private, and there is a little features only available in internal modules.Trenchant
Well, the internal modules are supposed to be for internal use only which works perfectly in Kotlin app. There is nothing to be exposed there. The problem is with interoperability from Kotlin to Java and not with the design I believe.Plume
you should think why the feature must be access round in module. maybe it can be down to the package visibility. I'm not good at English. so I wish you can understand it.Trenchant
I understand that making it private is the way but the problem is that class is very big and making it inner class will further reduce the readability of the class.Plume
@user25 I don't believe that article. It's examples are also not valid. For instance the smart cast error can be solved simply by adding as Int.Plume
I'll try to answer your question of how to redesign your code. do you mind?Trenchant
Sure this is the class.Plume
Why do you want to prevent public visibility of these classes in Java? Does it compromise the security of your library if called from Java, or do you just want not to give any compatibility guaranties for them?Igor
@Igor they are there only for internal use and has no purpose of being exposedPlume
Any solution???Frau
@Frau I am now using internal object with @JvmSynthetic internal fun which does the thing. Check here github.com/ryan652/EasyCrypt/blob/master/easycrypt/src/main/…Plume
Thank you so much!Frau
T
8

I have seen all of your internal classes are all about encrypt & decrypt.

you can do it easily by define a top-level function and mark it as @JvmSynthetic, and then makes the ECryptSymmetricDecrypt and ECryptSymmetricEncrypt classes to private to prevent Java client access your internal classes, for example:

// define this top-level function in your ECryptSymmetricEncrypt.kt

@JvmSynthetic internal fun <T> encrypt(
                                       input:T, password: String, cipher:Cihper, 
                                       erl: ECryptResultListener, outputFile:File,
                                       getKey:(String,ByteArray)->SecretKeySpec){

  ECryptSymmetricEncrypt(input, password, cipher,
                { pass, salt -> getKey(pass, salt) }, erl, outputFile)
}

However, it solved your problem, but I still want to say that your code can break into small pieces as further. for example, the encrypt & decrypt algorithm have many duplications, maybe you can applies Template Method Pattern in your encrypt library & introduce interfaces to make your library explicitly and hiding the Cipher operations under the implementation classes. Ideally, the client code can't see any java.security.* classes via Encrypt or Decrypt interfaces. for example:

interface Encrypt{
   //          v--- don't include the infrastructure class here,e.g:`Keys`,`Cipher`
   fun encode(...args)
}

interface Decrypt{
   //          v--- don't include the infrastructure class here,e.g:`Keys`,`Cipher`
   fun decode(...args)
}

AND it is a bad thing that you create an instance and compute the result in init block here.

AND you can use Factory Method Pattern to avoid the type checking both in ECryptSymmetricDecrypt and ECryptSymmetricEncrypt classes.

Trenchant answered 29/7, 2017 at 20:49 Comment(6)
Using your suggestion of top-level internal function generates an error saying internal function exposes its private return type ECryptSymmetricEncrypt.Plume
@Ryan are you define the top-level function in the related classes?Trenchant
@Ryan ooh. I know why. you shouldn't return the ECryptSymmetricEncrypt in your top-level function encrypt, since it is private. you should make the top-level function's return type to Unit/ Any.Trenchant
Yes that solves it. Now how do I call the method from another class?Plume
@Ryan you should apply the same rule as encrypt top-level functions. then in your Kotlin client code you can the top-level function encrypt instead.Trenchant
Let us continue this discussion in chat.Plume
P
8

Apart from @JvmSynthetic, you can use @JvmName with an illegal Java identifier, like adding a space.

As an example, I added a space in the @JvmName param, so any languages except Kotlin will not be able to invoke your method:

@JvmName(" example")
internal fun example() {
}
Perfectionism answered 13/11, 2017 at 5:39 Comment(3)
that seems so awful, but clever and technically solves the problem so upvoted ;)Forerunner
@ChelseaUrquhart Cheers :-)Perfectionism
@Perfectionism Geniuss!!Load
G
2

As per my answer on this question in another thread:

Not perfect solution but I found two hacky solutions

Annotate every public method of that internal class by @JvmName with blank spaces or special symbols by which it'll generate syntax error in Java.

For e.g.

internal class LibClass {

    @JvmName(" ") // Blank Space will generate error in Java
    fun foo() {}

    @JvmName(" $#") // These characters will cause error in Java
    fun bar() {}
}

Since this above solution isn't appropriate for managing huge project or not seems good practice, this below solution might help.

Annotate every public method of that internal class by @JvmSynthetic by which public methods aren't accessible by Java.

For e.g.

internal class LibClass {

    @JvmSynthetic
    fun foo() {}

    @JvmSynthetic
    fun bar() {}
}

Note:

This solution protects the methods/fields of the function. As per the question, it does not hide the visibility of class in Java. So the perfect solution to this is still awaited.

Groovy answered 17/7, 2020 at 10:24 Comment(0)
S
0

Utilizing a private constructor + companion object containing method to instantiate annotated with JvmSynthetic preserves encapsulation.

// Private constructor to inhibit instantiation
internal class SomeInternalClass private constructor() {

    // Use the companion object for your JvmSynthetic method to
    // instantiate as it's not accessible from Java
    companion object {
        @JvmSynthetic
        fun instantiate(): SomeInternalClass =
            SomeInternalClass()
    }

    // This is accessible from Java
    @JvmSynthetic
    internal var someVariable1 = false

    // This is accessible from Java
    @JvmSynthetic
    var someVariable2 = false



    // This is inaccessible, both variable and methods.
    private var someVariable3 = false
    @JvmSynthetic
    fun getSomeVariable3(): Boolean =
        someVariable3
    @JvmSynthetic
    fun setSomeVariable3(boolean: Boolean) {
        someVariable3 = boolean
    }
}
Steal answered 12/12, 2020 at 2:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.