blob: 815c8e2d82bda535e56351620ac80df1ec86149d [file] [log] [blame]
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
//
= Backwards compatibility support
:jbake-type: wiki
:jbake-tags: wiki, devfaq, needsreview
:markup-in-source: verbatim,quotes,macros
:jbake-status: published
:syntax: true
:description: Backwards compatibility support
:icons: font
:source-highlighter: pygments
:toc: left
:toc-title:
:experimental:
NetBeans contains deprecated obsolete code, which is typically left in place
for several releases. In addition to add polution to the API, it also increases
the number of dependencies to both ancient modules and Java platform. The
deprecated code is a dead weight in the released NB product, as the shipped
modules are (or should be) upgraded to work with API modules in their current
versions, not using deprecated APIs.
The purpose of this backward compatible support is to preserve binary
compatibility for unmaintained modules, or 3rd party modules with a different
release cycle while allowing to remove obsolete code from the public APIs.
The following techniques can be used for backward compatibility:
[[Accessor_method]]
== Accessor method
A compatible implementation may need to access the internals possibly from a
different module (classloader).
link:http://bits.netbeans.org/dev/javadoc/org-openide-modules/org/openide/modules/PatchedPublic.html[@PatchedPublic]
annotation currently serves this purpose.
The annotated method is *patched* to be public at runtime, while the class is
being loaded. The calling code is typically resides in the same package,
although a different module with an implementation dependency. Using
_@PatchedPublic_ it can access the method even at runtime, although from a
different classloader.
Note that this approach _still_ requires that method signature dependencies
affect the API module dependency closure. All types referenced from the
signature must be present for the compilation and execution of the API module.
If the referenced type contains an illegal platform or library dependency in
its API/impl, then the illegal component infects even the API module.
[[Automatic_module_dependencies]]
== Automatic module dependencies
If some classes are *split* from module "A" into a different module (say B),
source and binary compatibility can be retained if the module "A" declares "B"
as an additional implied dependency for clients who depend on an older version
of the module. Clients compiled against older version will receive the
additional dependency at both run-time and compile-time.
During compilation, a special file in _config_ area will be generated. The
generated file will be recognized when dependent module load, and their
dependencies will be transformed according to the description.
The automatic dependencies must be stored in the file
`module-automatic-deps.xml` in the module project's root folder. A typical
example of dependencies implied when a module is split to several ones is shown
below:
[source,xml,subs="{markup-in-source}"]
----
<!DOCTYPE transformations PUBLIC "-//NetBeans//DTD Module Automatic Dependencies 1.0//EN" "link:http://www.netbeans.org/dtds/module-auto-deps-1_0.dtd[http://www.netbeans.org/dtds/module-auto-deps-1_0.dtd]">
<transformations version="1.0">
<!-- unimportant content -->
<transformationgroup>
<description>Separation of desktop and cleanup</description>
<transformation>
<trigger-dependency type="older">
*<module-dependency codenamebase="org.openide.filesystems" spec="9.0"/>*
</trigger-dependency>
<implies>
<result>
*<module-dependency codenamebase="org.openide.filesystems.nb"/>*
*<module-dependency codenamebase="org.openide.filesystems.compat8"/>*
</result>
</implies>
</transformation>
</transformationgroup>
</transformations>
----
[[Module_Fragment]]
== Module Fragment
If a class in a module A patches a class in module B, the system must esnure proper visibility between A and B classloaders. With the Compatible Superclass approach, the compatibility class in A typically uses types defined by B, but B must see A's contents at run-time as B class will be made to extend A type (see below). The simplest way is to join contents of A and B in the same classloader.
If a module's MANIFEST.MF defines OpenIDE-Module-Fragment-Host: header, the module becomes a Module Fragment and its contents is included into the fragment host's module
classloader.
[[Example]]
=== Example
This is an example MANIFEST.MF of openide.filesystems module:
[source]
----
Manifest-Version: 1.0
OpenIDE-Module: *org.openide.filesystems*
OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/Bundle.properties
OpenIDE-Module-Layer: org/openide/filesystems/resources/layer.xml
OpenIDE-Module-Specification-Version: 9.0
----
A compatibility support module, which needs to merge with filesystems API at runtime uses the following MANIFEST:
[source]
----
Manifest-Version: 1.0
OpenIDE-Module: org.openide.filesystems.compat8
OpenIDE-Module-Localizing-Bundle: org/openide/filesystems/compat8/Bundle.properties
OpenIDE-Module-Specification-Version: 9.0
*OpenIDE-Module-Fragment-Host: _org.openide.filesystems_ *
----
There's no dependency from the *real API module* to the patch; the patch
depends on the API module. The patch module may be eventually not present at
all, if compatibility is not needed.
[[Compatible_superclass]]
== Compatible superclass
Because of JVM definition of method resolution, JVM looks not only in the class hosting the target method and specified as part of the Method Reference, but also in _superclasses_ of that class. It's therefore binary-compatible to *move the methods to some superclass*.
We must still prevent the superclass from appearing in the `extends` clause of the source, in order not to retain the dependencies from the superclass' dependency closure (the requirement was to avoid them). At run-time, the API class A which was compiled as extending superclass S, will be patched to extend another superclass, C. Provided that C extends S, type checks in the
running JVM should not be affected. The superclass C can then add methods with illegal dependencies in their transitive dependency closure.
The class which delivers the binary-compatible implementation must be annotated using *@PatchFor* annotation, which also identifies the
target class which should be modified at run-time. To preserve inheritance hierarchy properties, there are some rules to be followed.
Given API class "A" which extends "X", and binary-compatible implementation class "A"
* I must also extend X
* I must define the constructors with the same signature as X
* A must contain a default constructor, implicit or explicit
In addition, A and I must be loaded by the same classloader. To instruct NetBeans module system to do so, the module that contain I must
list the following Manifest entry:
[source]
----
OpenIDE-Module-Fragment-Host: codename
----
where the `codename` identifies the original module which contains API class A.
[[Example_2]]
=== Example
The AbstractFileSystem, in version 8.0 and earlier contains a number of @deprecated or obsolete methods:
[source,java,subs="{markup-in-source}"]
----
public abstract class *FileSystem* {
public abstract SystemAction[] getActions();
@Deprecated
public void prepareEnvironment(*FileSystem.Environment* env) throws EnvironmentNotSupportedException {
...
}
...
}
----
The methods are now moved to a class *FileSystemCompat*, which resides in a different module - _openide.filesystems.compat8_:
[source,java,subs="{markup-in-source}"]
----
*@PatchFor(_FileSystem.class_)*
public abstract class FileSystemCompat {
public abstract SystemAction[] getActions();
@deprecated
public void prepareEnvironment(*FileSystem$Environment* env) throws EnvironmentNotSupportedException {
...
}
...
}
----
The example also shows, how a _static member type_ may be moved to a deprecated module; JVM signature does not contain information that _FileSystem.Environment_ is a member type. *FileSystem$Environment* has the same signature.
[[Constructor_delegate]]
== Constructor delegate
API class A may have a constructor, which is no longer acceptable, because of
its signature dependencies. If the constructor was just implemented in an
'unlucky' way, the implementation could be lobotomized, but if the
constructor's signature contain an unwanted dependency, it should be rather
removed at all from the class.
To preserve backward compatibility, the constructor has to be added back at
run-time. Although JVM linking algorithm would eventually find _<init>()V_
method to call after new, the constructor "inherited" from the superclass would
not be able to initialize the API class fields.
The initialization of the original API class is implemented by its default
constructor - this means the API class *must have default constructor*, even
though it is private. _Delegation to other A constructors is not implemented
yet, but is feasible._
Initialization of the superclass, or possibly setup of API (A) fields are
delegated to a _static "factory" method_ in the `@PatchFor` superclass. The
initialization method must be annotated with *@ConstructorDelegate*. It's first
parameter must be of type of the compatible superclass itself and the rest of
parameters must be the same as the to-be-generated constructor in the API
class. Modifiers and declared exceptions are copied to the generated
constructor.
[[Example_3]]
=== Example
`JarFileSystem` has a constructor which takes *FileSystemCapability*. Since the
type is long deprecated and we want to remove it, the relevant implementation
moves off to the patch superclass:
[source,java,subs="verbatim,quotes"]
----
*@PatchFor(JarFileSystem.class)*
public abstract class JarFileSystemCompat extends AbstractFileSystem {
public JarFileSystemCompat() {
super();
}
*@ConstructorDelegate*
public static void createJarFileSystemCompat(_JarFileSystemCompat jfs_, *FileSystemCapability cap*) *throws IOException* {
FileSystemCompat.compat(jfs).setCapability(cap);
}
...
}
----
The *1st* argument of the `@ConstructorDelegate` method receives the newly
created instance to be initialized. Since `AbstractFileSystem` does not (in
sources) derive from FileSystemCompat, some runtime-typing magic must be done.
In effect, the bytecode generator creates a constructor in `JarFileSystem`:
[source,java,subs="verbatim,quotes"]
----
public JarFileSystem(*_FileSystemCapability cap_*) *_throws IOException_* {
this();
setCapability(cap);
}
----
[NOTE]
====
The content in this page was kindly donated by Oracle Corp. to the Apache Software Foundation.
This page was exported from link:http://wiki.netbeans.org/BackwardCompatibilityPatches[http://wiki.netbeans.org/BackwardCompatibilityPatches] , that was last modified by NetBeans user Sdedic on 2014-05-07T12:45:06Z.
This document was automatically converted to the AsciiDoc format on 2020-03-12, and needs to be reviewed.
====