5

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?

priyank
  • 2,651
  • 4
  • 24
  • 36
  • 1
    you can down to use *private* visibility. the visibility of top-level types is *package* visibility in Java. – holi-java Jul 29 '17 at 19:52
  • To maintain the readability of code I can't make them private. – priyank Jul 29 '17 at 19:53
  • 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. – holi-java Jul 29 '17 at 19:55
  • 1
    @holi-java That's too bad. Probably will have to sacrifice modularity and make them `private` – priyank Jul 29 '17 at 19:57
  • 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. – holi-java Jul 29 '17 at 20:00
  • 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. – priyank Jul 29 '17 at 20:04
  • 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. – holi-java Jul 29 '17 at 20:08
  • 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`. – priyank Jul 29 '17 at 20:14
  • @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`. – priyank Jul 29 '17 at 20:17
  • I'll try to answer your question of how to redesign your code. do you mind? – holi-java Jul 29 '17 at 20:19
  • Sure [this](https://github.com/ryan652/EasyCrypt/blob/master/easycrypt/src/main/java/com/pvryan/easycrypt/symmetric/ECryptSymmetricEncrypt.kt) is the class. – priyank Jul 29 '17 at 20:20
  • 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? – Ilya Jul 30 '17 at 04:05
  • @Ilya they are there only for internal use and has no purpose of being exposed – priyank Aug 02 '17 at 01:01
  • Any solution??? – vihkat May 15 '18 at 18:14
  • 1
    @vihkat I am now using `internal object` with `@JvmSynthetic internal fun` which does the thing. Check here https://github.com/ryan652/EasyCrypt/blob/master/easycrypt/src/main/java/com/pvryan/easycrypt/symmetric/performEncrypt.kt – priyank May 15 '18 at 19:48
  • Thank you so much! – vihkat May 16 '18 at 08:20

4 Answers4

7

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.

holi-java
  • 29,655
  • 7
  • 72
  • 83
  • Using your suggestion of top-level internal function generates an error saying _`internal` function exposes its `private` return type `ECryptSymmetricEncrypt`_. – priyank Jul 29 '17 at 21:31
  • @Ryan are you define the top-level function in the related classes? – holi-java Jul 29 '17 at 21:34
  • @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`. – holi-java Jul 29 '17 at 21:38
  • Yes that solves it. Now how do I call the method from another class? – priyank Jul 29 '17 at 21:41
  • @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. – holi-java Jul 29 '17 at 21:42
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/150503/discussion-between-ryan-and-holi-java). – priyank Jul 29 '17 at 21:42
7

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() {
}
ice1000
  • 6,406
  • 4
  • 39
  • 85
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.

Shreyas Patil
  • 808
  • 6
  • 17
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
    }
}
05nelsonm
  • 311
  • 3
  • 5