Import specifications (was Re: Import constraints)

Bryan Atsatt bryan.atsatt at oracle.com
Tue Jun 12 15:44:51 PDT 2007


Stanley M. Ho wrote:
> Hi Bryan,
>
> Bryan Atsatt wrote:
>> ...
>> By having Dependency extend Query, instances can be directly passed to
>> Repository.find(); no conversion required. We could obviously just make
>> a getter for the Query if we want, but why not make this convenient to
>> use?
>
> An import dependency describes the relationship between two modules.

Agreed. And I think we would be far better off thinking of and modeling
this relationship as a general *requirement specification*, rather than
using the very narrow definition:

    name + version(s)

Clearly these are important elements of an import specification. But the
design should allow for and even support others.

One concrete example is attributes. What is the point of declaring them
on a module if they cannot be used in an import specification? And I
think these could be very valuable.

The term "constraint" makes sense when you think of name as the primary
specification, and everything else as modifiers of it.

But this puts all the emphasis on the wrong syLLable, IMO. What will we
do when a "contract" model is introduced, where attributes and
version(s) are all that is required to specify the desired contract?

Module name won't be required at all in such a specification.


So far, we have three distinct actors in the resolution process:

1. Developer: creates import specifications, using whatever selection
criteria makes sense.

2. Admin: creates import specification *filter* (ImportOverridePolicy),
mapping developer specification to fit the local environment. (Can also
use a VisibilityPolicy, but it isn't clear to me why that same
functionality can't be achieved in the override policy.)

3. Module System: locates definitions that match specifications.

All of these actors collaborate to produce a single list of candidate
definitions at runtime. If that list contains duplicate packages, the
module system must select the best fit, or fail.

Clearly the Query mechanism provides an extensible solution to the
problem in #3. And annotations give us an extensible solution to the
problem in #1.

But, just as clearly, we have a big gap in the middle: the filter model
is not a general, extensible mechanism. And I think we need to fix this.

It seems to me that the best solution is to use the *same* model to
filter as we use for lookup: Query.

(Please see the "Query optimization" thread for my proposal to change
Query from an opaque to a transparent type.)

So, rather than pass VersionConstraint instances through the narrow()
method, we can pass Query instances. Yes, this does make it a bit more
work to implement such a filter, but it is then a completely extensible,
symmetric model.

And this is also why I proposed that we change ImportDependency to
simply take a Query as a ctor argument:

   public ImportDependency (Query spec,
                            boolean optional,
                            boolean reExport) {...}

(I should have sent my "optimization" proposal before I suggested the
above change, so it would've made more sense. Really this would be so
much easier if you could just read my mind :^)


Further, I don't understand the need to limit the filter to only
"narrowing" operations. For example, why shouldn't an admin be able to
map a module *name* as well? This could be really handy (and might even
allow for elimination of the refactoring/name problem).

In fact, why not allow the admin to be able to filter *any* part of the
import specification? I would prefer to see a more generic model than
the current ImportOverridePolicy, for example:

public class ImportOverridePolicy {
    public List<ImportDependency> getImports(ModuleDefinition def) {
        return def.getImportDependencies();
    }

    public static ImportOverridePolicy getPolicy() {...}
    static void setPolicy(ImportOverridePolicy policy) {...}
}

The default implementation does nothing, but subtypes can perform any
transformation they want. The ModuleSystem just calls this method
instead of calling the definition directly.

> It is true that we may want to query a module described in the import
> dependency, but an import dependency itself is not a query. In fact,
> someone may even want to query a module which contains the specific
> import dependency, although I think this use case is very rare. Anyway,
> it sounded like the main benefit to have import dependency extend query
> is to make it easier to pass into Repository.find(). Unfortunately, once
> a class is extended from another, all the public/protected
> methods/fields from the parent class would automatically become part of
> the signature of the child class.

No kidding? :^)

> I don't think we want to pollute the
> class hierarchy and potentially confuse developers just for a bit of
> convenient. ;-)

This is a style issue, and not one I care a great deal about--it
certainly is not the focus of my original message! But I will say that
convenience as a factor in design decisions should not be taken lightly.
To me, a clumsy usage model is a strong smell indicating that the
abstractions are not quite correct.

>
>> Second, I think we should eliminate VersionConstraint as a separate
>> class. It's functionality is easy to replace with Query elements that
>> match Version or VersionRange, strung together with appropriate boolean
>> operators.
>
> There are methods in VersionConstraint for determining if it contains a
> specified version/version range/version constraint. These methods are
> used heavily for version constraint override in module
> initialization/import policy/import override policy, and they are not
> suitable to be moved into the Query class. I think VersionConstraint
> should stay as a separate class.
>
>> Third, we should add a getExportedPackages() method to ModuleDefinition.
>> This way a Query can easily be created to search by package name.
>> Whether or not we choose to surface import-by-package declaratively, it
>> should at least be possible for a ModuleSystem to use such a Query.
>
> Having something like getExportedPackages() sounds reasonable from the
> perspective of interoperability.
>
>> Fourth, we should add support for simple attribute constraint
>> declarations. Enabling simple equality constraints ought to be easy
>> enough: an AttributeConstraint annotation that takes key/value lists
>> (key1==value1;key2==value2;...). We already have support for expressing
>> these as Query instances.
>
> The Query class currently only supports querying arbitrary attributes at
> the module level. Are you suggesting that we should let developers to
> declare arbitrary attributes at the import?

Yes.

> Could you come up with real use cases for this in the context of 277 alone?

Sure: import-by-contract. One such contract is a specification name;
together with version info, a Specification-Name attribute could be used
to select a definition.

Out current spec describes "attribute matching" as one use case for
implementing a custom ImportPolicy. If there is any value in enabling
this, then we should probably support it directly "at the import".


>
>> Finally, Query ought to have a toString() implementation so that we can
>> log them, examine in a debugger, put them in error messages, etc.
>
> The Query.toString() will output a human readable string, and this is
> already implemented in the RI.
>
> - Stanley
>



More information about the jsr277-eg-observer mailing list