Veripacks 0.2: exporting subpackages

Veripacks 0.1 allowed to specify which classes should be exported from a package hierarchy, by using a simple @Export annotation, and later verify that the specification is met. Version 0.2 extends this by allowing to export subpackages as well, using the @ExportSubpackages package annotation.

When exporting a subpackage, only the classes exported by the subpackage will be re-exported (same holds for sub-subpackages). This is in line with Veripacks respecting the hierarchical structure of packages.

(Previously, either all subpackages and all classes where exported – when no @Export annotations were present, or no subpackages were exported, if at least one @Export annotation was present.)

There are also two new convenience package annotations, @ExportAllClasses and @ExportAllSubpackages, which have pretty self-explaining names.

How does this work? An example will illustrate it best, using some imaginary syntax:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package foo.bar.p1.sub_p1 {
   @Export
   class Sub1 { ... }
}
 
package foo.bar.p1.sub_p2 {
   @Export
   class Sub2 { ... }
 
   class Sub3 { ... }
}
 
// Normally the annotation would go to package-info.java
@ExportSubpackages("sub_p1") 
package foo.bar.p1 {
   @Export
   class A { ... 
      // Legal, Sub1 is exported by sub_p1
      new sub_p1.Sub1() 
      // Legal, Sub2 is exported by sub_p2
      new sub_p2.Sub2() 
 
      // Illegal, Sub3 is not exported by sub_p2
      new sub_p2.Sub3() 
   }
}
 
package foo.bar {
   class B {
      // Legal, A is exported by p1
      new p1.A() 
 
      // Legal, sub_p1 is exported by p1, and Sub1 is exported by sub_p1 
      new p1.sub_p1.Sub1() 
 
      // Illegal, sub_p2 is not exported by p1 
      new p1.sub_p2.Sub2() 
   }
}

How can this be useful? Veripacks goes beyond what can be achieved with normal Java access modifiers (especially public and package-private), by extending the meaning of packages to a hierarchical structure. It also aims to replace, in some cases, the need for separate build modules.

What’s next? As mentioned in the README, the plans are to add a possibility to require an explicit import of a package, to be able to use its classes. And of course, some IDE support to be able to quickly see what is exported and where would be great.

The release is available in SoftwareMill’s maven repository, and the code is available under the Apache2 license on GitHub.

Have fun!
Adam

  • Stefan Zeller

    Hi Adam

    I think about to create experimental tool support for IntelliJ. I would like to start with an inspection. That means while editing a java file all classes in this file have to verified. In contrast to the provided Verifier that verifies a whole package. My approach would be to resolve all references and check if these are allowed to be used within the class currently verified.

    The problem is, while its easy to check that a reference is annotated with @Export or a package in it’s package hierarchy has got @ExportAll it kind of hard (not hard but expensive) to check if a reference is NOT allowed to be used. For the latter I have to check all classes in same package if one is annotated with @Export.

    What do you think, do I have a wrong approach or is there a way to make the prohibition of exporting more explicit (instead of implicit prohibit if at least one class is annotated with @Export — thats kind of magic)?

    IMHO, more explicit rules would increase transparency of veripacks.

    Greetings

    Stefan

  • http://www.warski.org/ Adam Warski

    Hey,

    sorry for the late reply, I was on a conference. An IntelliJ plugin would be awesome, I’m a daily user of Idea as well :).

    I agree that checking that a class cannot be used can be expensive. The problematic rule here is that if there are no annotations at all, everything is exported. It would be certainly nice to have a requirement to have an explicit annotation, but then, wouldn’t it be a pain to have to annotate every package? Or on the other hand, having an explicit annotation to hide classes, would mean annotation almost every class.

    Or maybe you have some other ideas on how to make the rules more explicit?

    Certainly “magic” is no good, but the whole system needs to be useable, so as to get the desired effect with minimum effort from the user (users are lazy ;) )

    As to making the expensive checks cheaper, my only idea right now is to constantly hold in-memory the current state of the annotations. Not sure if IntelliJ provides some facilities for that. Keeping this in sync could be hard.

    Adam

  • Stefan Zeller

    Hi Adam

    I thought about rules and magic.

    What about the following rule set:
    1. Default rule is public
    2. You can annotate a package @Private. That makes all including classes and subpackages private. Thus, rules get inherited.
    3. A package or a class can overwrite rules with declaring @Public (or in you vocabulary, @Export)

    Therefore,
    - it’s backward compatible: No annotation means public
    - no magic: One can either see a restricting annotation on a class or a restricting annotation on its hierarchy of packages (tool and human friendly :-D)

    What do you think?

    Stefan

  • http://www.warski.org/ Adam Warski

    So if I understand correctly, to make only one class from a package available, I would have to:
    - annotate the package as @Private
    - annotate the class as @Public?

    And to make only one subpackage public, I would have to:
    - annotate the package as @Private and @PublicSubpackage(“sub1″)?

  • http://www.warski.org/ Adam Warski

    Thanks for the input.

    So if I understand correctly, to make only one class from a package available, I would have to:

    - annotate the package as @Private
    - annotate the class as @Public?

    And to make only one subpackage public, I would have to:
    - annotate the package as @Private and @PublicSubpackage(“sub1″)?

    Adam

  • Stefan Zeller

    First (let’s say) use case: Yes, that would be the implication. You have to annotate two ‘elements’ but it’s transparent.

    Second use case: This example shows, that this topic is not as easy as thought three days ago :)

    My first thought is, that annotating a parent package to make a child package visible is also kind of not transparent. Because, when I refactor the child package I have to refactor the annotation on parent package as well. Also, when I just view the child package I’m unsure about it’s visibility . Could be public (because that’s the default) or it could be private because of inheritance of any package in parent hierarchy.

    Okay, I refine my approach to handle the Second use case more explicit:

    The default visibility is ‘inherited’. Then I explicit know by looking at a class or package if it’s private, public or inherited.

    That would result in following for user case 2 (make only one subpackage public):
    - annotate parent package as @Private
    - annotate child package as @Public
    - all other child packages are inherited (@Private nor @Public)
    or
    - annotate child package as @Public
    - annotate all other child packages @Private

    Finally, inheritance of comes with potential problems. I have to be very careful to change visibility of any package, because the functionality of some package or class in sub hierarchy could be broken. Also when I move a not annotated class from one package to another, the visibility could changed (depending on the visibility of the new parent package or any parent package in the hierarchy). This sounds kind of fragile to me. Without a rigid tool support that is error prone. One alternative would be no inheritance and default is @Public…

  • http://www.warski.org/ Adam Warski

    Very good point about the child package annotations & refactoring. In fact, currently when you want to export a class, you annotate the class, but when you want to export a package, you annotate it’s “container” (parent package). I’m not sure what is the right way here. Annotating the child package directly may seem more consistent, but the it would really define the behavior “two levels up” (if the package is visible outside of the parent package) – may be hard to grasp mentally at first.

    So in the end, I’m not sure which approach to exporting subpackages is better. Doing it on the parent is easy to understand, but refactoring-error-prone, doing it on the child package is harder to understand, but more consistent.

    The inheritance you describe wouldn’t work well I guess… apart from the problems you mention, if the parent package has @Private, then how would the @Public child package be exported? Would you be able to hide it at all, at a higher level?