| // |
| // 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. |
| ==== |