Split Packages and Bootstrap Jars in Java 9 - java

It seems that in Java 9 it is not allowed to have so-called Split Packages, i.e. the same package being defined in two different modules. This causes a problem with my migration process: The (Gradle) project contains a Jar file that is called bootstrap.jar, with a structure like this:
bootstrap.jar
- com
- example
- Foo.class
- Bar.class
- Baz.class
The src directory contains a class com.example.Bar that depends on Foo as well as a module definition, for com.example. The bootstrap.jar file does not contain a module info, as it was compiled before Java 9, so it uses an automatic module called bootstrap. The problem is that now the package com.example is defined in both modules, com.example and bootstrap.
The reason there is this bootstrap.jar file, to begin with, is as follows:
The src/com/example folder actually contains Bar.java, Baz.java and another file, Foo.dyvil. The latter is written in a JVM-based programming language. So the dependency chain looks like this:
Bar.java -> Foo.dyvil -> Baz.java
During the build process, it gets compiled to Foo.class, which gets placed in a new Jar file that later replaces bootstrap.jar. The reason all these files are placed is that both the Java and Dyvil compiler cannot process the other languages files, so they require some access to the compiled classes from the previous build. So that is why there is bootstrap.jar.
Now for the actual problem: Since split packages are disallowed in Java 9, is there any way to achieve "split builds" using "bootstrap" jar files as described and used in my project? Or is there any other approach?

Though the long-term solution to this is resolving such packages to exist in a single module and then modularising the code.
As a temporary solution, you can make use of the option:-
--patch-module <module>=<file>(<pathsep><file>)*
as in your case
--patch-module com.example=bootstrap.jar
Do keep in mind though, the --patch-module option is intended only for testing and debugging. Its use in production settings is strongly discouraged.

Related

Why does the Java Extension Mechanism not check the classpath for an optional package implementing LocaleServiceProvider?

I have created a maven project L and written a Java extension (i.e. an optional package) implementing (i.e. extending) the (abstract) service providers that implement (i.e. extend) LocaleServiceProvider, to support a dialect (let's call it xy) that isn't normally supported by the JRE. (I do not want to use the CLDR extension that came with Java 8, even though I'm running 8.141.)
The project compiles, and produces a jar with a META-INF/services folder that contains the provider-configuration files in UTF-8 with the qualified provider class names being on a line that ends with a line feed (\n).
I have then declared a maven dependency in my project P on the locale project L, and I thought that that would work, because the tutorial states
The extension framework makes use of the class-loading delegation
mechanism. When the runtime environment needs to load a new class for
an application, it looks for the class in the following locations, in
order:
[...]
The class path: classes, including classes in JAR files,
on paths specified by the system property java.class.path. If a JAR
file on the class path has a manifest with the Class-Path attribute,
JAR files specified by the Class-Path attribute will be searched also.
By default, the java.class.path property's value is ., the current
directory. You can change the value by using the -classpath or -cp
command-line options, or setting the CLASSPATH environment variable.
The command-line options override the setting of the CLASSPATH
environment variable.
Maven puts all dependencies on the classpath, I believe.
Yet when I run my unit test in P (in IntelliJ; L is on the classpath), it fails:
#Test
public void xyLocalePresent() {
Locale xy = new Locale("xy");
assertEquals("P not on classpath", xy, com.example.l.Locales.XY); // access constant in my Locale project L; should be equals to locale defined here
SimpleDateFormat df = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, xy);
assertEquals("dd/MM/yy", df.toPattern()); // fails; L specifies the short date pattern as dd/MM/yy
}
I have to start it with -Djava.locale.providers=SPI,JRE -Djava.ext.dirs=/path/to/project/L/target. If I do that, it works, indicating that L's service providers were loaded successfully (indicating the jar's structure is ok).
NB: the Java 8 technotes say that the order SPI,JRE is the default.
Why, oh why does it not work when I just put L on the classpath? Why do I have to point to it explicitly?
Update: After going through the JavaDoc again, I just saw this (emphasis mine):
Implementations of these locale sensitive services are packaged using
the Java Extension Mechanism as installed extensions.
That explains things. :(
Is there any way to make this work by just putting L on the classpath when P runs, i.e. without having to install L (or having to use -D system properties)? (P uses maven, Struts2 and Spring, if that helps...)
In more complex applications, such as web servers (e.g. Tomcat), there are multiple ClassLoaders, so each WebApp served by the web server can be kept independent.
The extension mechanism is for extending the core Java functionality, i.e. features available globally within the running JVM (the web server). As such, they must be loaded by the System ClassLoader.
The standard way to add an extension to the code Java runtime, is to either
add the Jar file to the JRE_HOME/lib/ext folder
add extra folders to be searched by specifying the java.ext.dirs system property
You could also just add it to the Bootstrap ClassPath yourself, but that might cause problems if the Security Manager is activated. Not sure about that part. So it's best to do it the official way.
Note that the classpath defined by the CLASSPATH environment variable, or the -cp command-line option, does not define the Bootstrap ClassPath.
To learn more, read the Java documentation "How Classes are Found".

SonarQube: source directory does not match package declaration

I'm having difficulty working out the correct properties for sonarQube for my project. The folder structure is as following:
mod/
framework/
Framework.java
rebuild2/
Rebuild2.java
sonar-project.properties
There isn't much yet in either java file but do have they do have package declarations (mod.framework and mod.rebuild2 respectively).
I've tried a variety of different ways to write the properties file but it always errors with "The source directory does not match the package declaration". It seems like it is expecting the folders to be mod.framework/ and mod.rebuild2/.
I've looked through the documentation on the main website but all their examples do not have package declarations. I've also searched through here but either the solutions do not work for this or they are maven/gradle configs.
Does anyone have any idea whether it is possible to set up the sonar-project.properties file for this situation?
# Required metadata
sonar.projectKey=mc:rebuild2
sonar.projectName=Rebuild 2
sonar.projectVersion=0.2
# The value of the property must be the key of the language.
sonar.language=java
# Encoding of the source code
sonar.sourceEncoding=UTF-8
sonar.modules=framework,rebuild2
framework.sonar.projectName=Framework
framework.sonar.projectBaseDir=mod
framework.sonar.sources=.
rebuild2.sonar.projectName=Rebuild 2
rebuild2.sonar.projectBaseDir=mod
rebuild2.sonar.sources=.
If Java file(say File.java) has package delacration like :
package com.abc.xyz;
for a file, It means that file should be inside folder structure like
com
abc
xyz
File.java
But in your case may be, this rule is voided. And your code should not compile too.
Please check, this may be the case.
There is something conceptually wrong in your configuration.
Either the 'Framework' and 'Rebuild 2' projects are separate modules (think sub-projects) that have separate source trees; or they are merely separate packages in the same source tree, in which case you can remove the lines below sonar.sourceEncoding=UTF-8.
Your configuration tries to define 2 modules in the same source tree, and I don't think this is supported by current analyzers, except using exclusions, e.g:
sonar.modules=framework,rebuild2
framework.sonar.projectName=Framework
framework.sonar.projectBaseDir=.
framework.sonar.sources=.
framework.sonar.inclusions=mod/framework/**
rebuild2.sonar.projectName=Rebuild 2
rebuild2.sonar.projectBaseDir=.
rebuild2.sonar.sources=.
rebuild2.sonar.inclusions=mod/rebuild2/**
See Multi-module Project configuration in the SonarQube documentation.

java.lang.NoClassDefFoundError for nested class at run-time? (Dependency problems?)

Ok, here's the situation:
I have three files:
1. TScan.java
2. Test.java
3. ScanServlet.java
I write my mail class, TScan in the TScan.java file, and inside that class, there is a nested class (not a sub-class), called TEntry. TScan has a main() used for testing and other methods that use the nested TEntry class with no problems.
Also, Test.java is a fancier testing program that uses items from TScan.java, including the TScan and TEntry classes with no problems. TScan.java and Test.java were developed and tested in Eclipse (and it compiles/runs fine from the command line as well).
So TScan is working to a useful point, and I wanted to include it in a webapp. Installed Jetty and wrote ScanServlet.java, which runs in Jetty, no problems.
I started referencing TScan and it started giving the error above for TScan. Changing the ClassPath to include the TScan.class file didn't work, so I made a JAR file and stuck it in the WEB-INF/lib/ folder of the WebApp, and now ScanServlet compiles filen, and seems to find TScan at run-time, however it gives the NoClassDefFounfError TScan$TEntry at run-time. This doesn't make sense to me, since:
1. Obviously it's finding the TScan class now (Since it's not complaining about that at compile or run-time)
2. The error occurs in the TScan.java file (TEntry isn't reference directly except by TScan), which runs fine in stand-alone mode!
3. I made TEntry public, and it still doesn't work.
I have found a lot of questions about NoClassDefFoundError, but none dealing with nested classes, especially with Jetty.
Bear in mind I am more used to Delphi, where if it compiles, it runs. (And ABAP which is more or less the same).
If someone has a solution I will be thrilled, otherwise, I am considering:
Copy-Paste the whole TScan into ScanServlet (Shouldn't have a problem finding it if it's in the same file!) (not very modular)
Move TEntry into a separate class in a separate .java file? (May be a pain to cleanly extract).
Have ScanServlet call TScan.Java as a command line program. (Slow...)
How have you created your jar file and does it contain TScan$TEntry.class in the correct folder?
An inner or nested class still produces a .class file which you need to ship
Looks like you are limping along because your not using any formal build tools that apply rigor and convention to your development. Before messing around what is where and how to manage your handful of classes...start using a real build system that produces a jar file that contains what you need. Maven, Ant, Buildr, whatever...something that applies some convention or configuration. Don't leverage anything any IDE gives you in terms of compiling or exporting something that is supposed to be runnable...you can't trust any of that bunk.
With a proper build mechanism and ideally declarative dependencies and the like you can address your issue in a iterative fashion to resolve the NCDFE. my 2 cents at least
[edit]
On your specific issue, I suspect your running into the webapp classloader configuration. By default classes in /lib are not exposed to webapp contexts. You will need to setParentLoaderPriority on the webapp context to true or bundle your classes as a war file. Also use --dry-run on the cli to help sort out what jar files are being loaded up by jetty.

Do Eclipse's Refactoring Tools Violate The Java Language Specification?

In Eclipse 3.5, say I have a package structure like this:
tom.package1
tom.package1.packageA
tom.package1.packageB
if I right click on an the tom.package1 package and go to Refactor->Rename, an option "Rename subpackages" appears as a checkbox. If I select it, and then rename tom.package1 to tom.red my package structure ends up like this:
tom.red
tom.red.packageA
tom.red.packageB
Yet I hear that Java's packages are not hierarchical. The Java Tutorials back that up (see the section on Apparent Hierarchies of Packages). It certainly seems like Eclipse is treating packages as hierarchical in this case.
I was curious why access specifiers couldn't allow/restrict access to "sub-packages" in a previous question because I KNEW I had seen "sub-packages" referenced somewhere before.
So are Eclipse's refactoring tools intentionally misleading impressionable young minds by furthering the "sub-package" myth? Or am I misinterpreting something here?
Eclipse can't possibly violate the JLS in this case, because it has nothing to do with compiling or running Java source or bytecode.
The refactoring tools behave as they do because that behaviour is useful to developers. The behaviour is useful to developers because, for many intents and purposes, we do treat packages as hierarchal (a.b.c has some kind of relationship with a.b, even if that relationship is not consistent from project to project). That doesn't mean Java treats them as hierarchal intrinsically.
One example where people treat packages as very hierarchal is in configuring a logging framework such as log4j. Again, it's not intrinsic to log4j, but that's how people use it in practice.
Java packages are not hierarchical in the sense that importing everything from package A does not import everything from package A.B.
However, Java packages do correspond directly to the directory structure on the file system, and directories are hierarchical. So Eclipse is doing the correct thing - it is renaming the directory, which automatically changes the name of the parent directory of the renamed directory's children (to state the very obvious).
even java itself has the concept of subpackage:
http://java.sun.com/j2se/1.5.0/docs/tooldocs/windows/java.html
java -ea[:<package name>"..." | :<class name> ]
Enable assertions. Assertions are disabled by default.
With no arguments, enableassertions or -ea enables assertions. With one argument ending in "...", the switch enables assertions in the specified package and any subpackages. If the argument is simply "...", the switch enables assertions in the unnamed package in the current working directory. With one argument not ending in "...", the switch enables assertions in the specified class.
If a single command line contains multiple instances of these switches, they are processed in order before loading any classes. So, for example, to run a program with assertions enabled only in package com.wombat.fruitbat (and any subpackages), the following command could be used:
java -ea:com.wombat.fruitbat... <Main Class>
Java's packages are not hierarchical, but Eclipse stores packages on your system's file structure.
tom.package1.packageA is represented on a Windows file system as tom/package1/packageA.
When you ask Eclipse to refactor a package name, you're asking Eclipse to change the name of the file system directory structure.
You can have packages in Eclipse like:
tom.package1.packageA
tom.package2.packageB
tom.package3.packageC
You'll just have different 2nd level file system directories.

Is it necessary that class className must be coded in className.java?

I'm using maven to build the project and compile failed because I put class Test2 in Test.java,
but is it because of maven or simply because java itself doesn't support this?
BTW,how can I open a maven project with eclipse?
A public class called ClassName must (in most cases) be defined in a file called ClassName.java.
Edit
Although this is not a requirement of the Java language, most (if not all) implementations of Java, the above mentioned relationship between the class name and file name must hold, or a compiler error will result.
(For more information, please refer to Jon Skeet's answer.)
The reason for this can be found by reading Section 7.2: Packages of The Java Language Specification, Third Edition.
In the section, it describes how the directory structure can be mapped to a fully-qualified class name in Java, which leads to a requirement that the .class file which contains the bytecode for a class must reside in a path that resembles that of the fully-qualified class name.
The original answer which incorrectly stated that the naming scheme is a requirement has been edited.
The Java Language Specification itself doesn't actually require this - but it explicitly allows file-system-based implementations to do so, and most do.
From section 7.6:
When packages are stored in a file
system (§7.2.1), the host system may
choose to enforce the restriction that
it is a compile-time error if a type
is not found in a file under a name
composed of the type name plus an
extension (such as .java or .jav) if
either of the following is true:
The type is referred to by code in
other compilation units of the package
in which the type is declared.
The
type is declared public (and therefore
is potentially accessible from code in
other packages).
This restriction
implies that there must be at most one
such type per compilation unit. This
restriction makes it easy for a
compiler for the Java programming
language or an implementation of the
Java virtual machine to find a named
class within a package; for example,
the source code for a public type
wet.sprocket.Toad would be found in a
file Toad.java in the directory
wet/sprocket, and the corresponding
object code would be found in the file
Toad.class in the same directory.
When packages are stored in a database
(§7.2.2), the host system must not
impose such restrictions. In practice,
many programmers choose to put each
class or interface type in its own
compilation unit, whether or not it is
public or is referred to by code in
other compilation units.
For practical purposes I think it's reasonable to basically assume that it's required.
Java requires you to have your public class inside a file with the same name.
For eclipse and maven use sonatype m2 plugin. Inside your maven project you can then type
mvn eclipse:eclipse
and maven will create a .project and a .classpath file for you. These are the files eclipse needs to work with the project.
You have to define the eclipse classpath variable M2_REPO with the path of your local maven repository.
With sonatype m2 you can do maven things from within eclipse: add dependencies, run maven targets, ...
The connection between file name and class name is the following:
if you have public class in a file, the filename must be the same as the class name
you can have as many non-public classes as you want in a file with different names
I'm using maven to build the project and compile failed because I put class Test2 in Test.java,
Source files must be named after the public class they contain, appending the suffix .java. In you case, the source file for the public class Test2 must be Test2.java 1.
is it because of maven or simply because java itself doesn't support this?
The Java compiler, javac, is complaining, not Maven (Maven doesn't change the behavior of the compiler).
how can I open a maven project with eclipse?
Either use the Maven Eclipse Plugin (i.e. a plugin for Maven) and simply run mvn eclipse:eclipse on your project and then Import... it as Existing Projects into Workspace in Eclipse. You will need to set a classpath variable M2_REPO pointing to the local repository to use it. Check the Usage page for the details.
Or (and that's an exclusive or) install m2eclipse (i.e. a plugin for Eclipse that extends it to make it able to understand a Maven projects and interact with it in a bi-directional way) and Import... your project as an Existing Maven Projects.
If EXCLUSIVE is not clear enough, this means: use one or the other but not both in the same time.
1 As mentioned by Jon Skeet, the JLS allows this restriction for file-based implementations. This doesn't apply when using a database to store Java constructs (like Visual Age for Java was doing). But in your case, it does.

Categories