Get javadoc version for a class? - java

I use the javadoc #version tag in my classes, however I am not sure how to get the version in the class itself. Let's look at an example...
/**
* #version 1.0a
* #author Redandwhite
*/
public class JClass extends JFrame implements ActionListener, KeyListener {
String version = ...
}
Is there a method/class that can retrieve the version? Ideally, it's as simple as an API call which returns a simple String.

The javadoc comments are not included in the generated byte code in any form, so there is no way to access the value of the #version tag from Java code (unless you parse the source code of course). There might be a version annotation that can be used to specify the class version instead of or in addition to the javadoc #version tag, and this annotation would be accessible via the Java reflection API (i.e. Class.getAnnotations()).

As it's already been noted, it's not possible to get to that information.
An alternative solution is to read the Implementation-Version/Specification-Version property of the package.
That value can be defined in the MANIFEST.MF file of a jar file. If it is defined, then it can be queried for every package that's contained in that jar file.
So if you have a class org.example.MyClass and the jar it comes in has the relevant entries in the MANIFEST.MF file, then you can get the version like this:
Class<?> clazz = org.example.MyClass.class
Package pkg = clazz.getPackage();
String version = pkg.getImplementationVersion();

Related

How can I filter out Java methods according to version?

I need to generate a jar library in three different versions. Some methods of the classes are marked with annotations that specify the version when they were added. For example:
public class A {
#SinceVersion(2)
public int getTotal() {
// do something...
}
#SinceVersion(5)
public int getMax() {
// do something...
}
#SinceVersion(4)
public int getAverage() {
// do something...
}
}
Then:
When I generate my-library-2.jar, only getTotal() should be included.
When I generate my-library-4.jar, only getTotal() and getAverage() should be included.
When I generate my-library-5.jar all three methods should be included.
This is a simplified example. The real problem spans 300 classes, 10 versions, with 6 subversions each.
You may wanna look into implementing an annotation processor.
Annotation processors run during compile time and are used to create and/or manipulate code, and I use them to create boilerplate code, as well as code that's otherwise tedious to maintain manually, but has a clear structure.
Why not use source control (git, etc.) to manage your releases? when you tag a release with v2.0, then code up to that point will have only getTotal(). Then v4.0 comes a long, and the code up to that point will have getTotal() and getAverage(), and so on.
Each release can be managed and built independently, and produce the specific jar file for that specific release.
Your client will then include whichever version of the jar it needs.
I wanted to post the solution I found in case is of help for someone else.
After researching for a week I finally found Apache Commons BCEL. This library allows you to inspect and modify a class loaded in the JVM; once modified it can be written to the file system as a .class file.
A piece of code like this produces a new class file without some methods:
import org.apache.bcel.Repository;
import org.apache.bcel.classfile.AnnotationEntry;
import org.apache.bcel.classfile.JavaClass;
import org.apache.bcel.classfile.Method;
import org.apache.bcel.generic.ClassGen;
JavaClass clazz = Repository.lookupClass("test.House");
ClassGen cg = new ClassGen(clazz);
Method m = findMethod(clazz, "close");
AnnotationEntry ae = m.getAnnotationEntries(); // process annotations here...
cg.removeMethod(m); // remove the method you want to remove
JavaClass modifiedClazz = cg.getJavaClass();
modifiedClazz.dump("./gen/test/House.class"); // save to a new file
By walking the whole tree of classes (or a subpackage tree) it's possible to find all affected classes, process their methods, and remove them if the annotation values match some criteria.

Inconsistent ClassCastException for XmlBeans.Factory parse method

I know this is a very long shot, but I've been trying to figure it out for like two weeks already, so any idea pointing to the right direction could be priceless.
So, I have a very old application which uses XmlBeans. My task is to migrate from Tomcat 7.0.67 to Tomcat 8.5.11 introducing Spring Sessions and Spring Security instead of Realm-based authentication. Prior the migration everything was working fine both locally (MacOS, Oracle JDK 8) and on Heroku (Ubuntu, OpenJDK 8), but after the migration everything works on my local environment, but on Heroku, sometimes, when the app tries to parse a string to appropriate XmlBean, this ClassCastException occurs:
java.lang.ClassCastException: foo.bar.2.impl.PreferencesDocumentImpl cannot be cast to foo.bar.1.PreferencesDocument
at foo.bar.1.PreferencesDocument$Factory.parse(Unknown Source)
I have two auto-generated by XmlBeans classes, which were generated from two xsd-schemas without any namespace set. Classes share the name, but are located in different packages (parse method where the exception occurs is located in the Factory inner class, other methods are omitted):
/*
* An XML document type.
* Localname: Preferences
* Namespace:
* Java type: foo.bar.1.PreferencesDocument
*
* Automatically generated - do not modify.
*/
package foo.bar.1;
public interface PreferencesDocument extends org.apache.xmlbeans.XmlObject {
public static final org.apache.xmlbeans.SchemaType type = (org.apache.xmlbeans.SchemaType)
org.apache.xmlbeans.XmlBeans.typeSystemForClassLoader(PreferencesDocument.class.getClassLoader(), "schemaorg_apache_xmlbeans.system.s2D5798E4F4AFDA8394735C8512CDCBC7").resolveHandle("preferencesa8bfdoctype");
public static final class Factory {
public static foo.bar.1.PreferencesDocument parse(java.lang.String xmlAsString) throws org.apache.xmlbeans.XmlException {
return (foo.bar.PreferencesDocument) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, null );
}
}
}
/*
* An XML document type.
* Localname: Preferences
* Namespace:
* Java type: foo.bar.1.PreferencesDocument
*
* Automatically generated - do not modify.
*/
package foo.bar.1.impl;
public class PreferencesDocumentImpl extends org.apache.xmlbeans.impl.values.XmlComplexContentImpl implements foo.bar.1.PreferencesDocument {
public PreferencesDocumentImpl(org.apache.xmlbeans.SchemaType sType) {
super(sType);
}
private static final javax.xml.namespace.QName PREFERENCES$0 = new javax.xml.namespace.QName("", "Preferences");
}
/*
* An XML document type.
* Localname: Preferences
* Namespace:
* Java type: foo.bar.2.PreferencesDocument
*
* Automatically generated - do not modify.
*/
package foo.bar.2;
public interface PreferencesDocument extends org.apache.xmlbeans.XmlObject {
public static final org.apache.xmlbeans.SchemaType type = (org.apache.xmlbeans.SchemaType)
org.apache.xmlbeans.XmlBeans.typeSystemForClassLoader(PreferencesDocument.class.getClassLoader(), "schemaorg_apache_xmlbeans.system.sC8953008EC716AA258D3951B84AB1CB7").resolveHandle("preferencesa8bfdoctype");
public static final class Factory {
public static foo.bar.2.PreferencesDocument parse(java.lang.String xmlAsString) throws org.apache.xmlbeans.XmlException {
return (foo.bar.2.PreferencesDocument) org.apache.xmlbeans.XmlBeans.getContextTypeLoader().parse( xmlAsString, type, null ); }
}
}
/*
* An XML document type.
* Localname: Preferences
* Namespace:
* Java type: foo.bar.2.PreferencesDocument
*
* Automatically generated - do not modify.
*/
package foo.bar.2.impl;
public class PreferencesDocumentImpl extends org.apache.xmlbeans.impl.values.XmlComplexContentImpl implements foo.bar.2.PreferencesDocument {
public PreferencesDocumentImpl(org.apache.xmlbeans.SchemaType sType) {
super(sType);
}
private static final javax.xml.namespace.QName PREFERENCES$0 =
new javax.xml.namespace.QName("", "Preferences");
}
}
Sometimes, when the app deployed to Heroku is restarted, the problem is gone, but after another restart it's back again.
According to this, the root cause is the absence of namespaces which leads to collision. But due to our requirements I can't add or change namespace of the xsds. So do you have any ideas why does it work locally with Tomcat 7, locally with Tomcat 8 and on Heroku with Tomcat 7, but doesn't work on Heroku with Tomcat 8?
Thanks in advance.
I suspect the unpredictable nature of the problem (i.e. sometimes it does or does not happen after a restart) is due to the non-determinisitic nature of the JVM classloader. If you have two different versions of the same class, they will be loaded in a non-deterministic order.
In this case, it sounds like you have two different classes with the same name (am I correct?). Even though they are auto-generated, only one will win.
I think you must find a way to give the classes different names (or packages).
It works on Tomcat 7 because of this.
Prior Tomcat 8 a Tomcat's ClassLoader was loading resources in alphabetical order. Our app was working only because of that.
It works locally on Tomcat 8 + MacOS, because Tomcat 8's classloader loads resources in an order provided by OS, which, in case of OSX, seems to be ordered.
This answer appears at the top for the following class of errors as well so I am adding my answer here:
class org.apache.xmlbeans.impl.values.XmlComplexContentImpl cannot be cast to class <class you generated a schema from>
This is a very bizarre error, after all why would this cast even be happening at all? In any case, this was because I was using the maven-shade-plugin minimizeJar feature and it was removing something (I do not know what yet), if I figure out what exactly I need to explicitly re-add I will update this answer, but for now I just disabled minimizeJar.
It seems it was removing the impl part of the schema generation code.
Update: add this to the filters section of the shade plugin if you want to continue using minimizeJar:
<filter>
<artifact>org.apache.xmlbeans:xmlbeans</artifact>
<includes>
<include>**</include>
</includes>
</filter>
Update 2: it seems that the above does not work all the time, I am very confused as to what the new issue is here, but the point still stands that minimizeJar is very volatile w.r.t dependencies

Can't reference generated-sources class outside of default package

I'm working with Google's Protocol Buffer (in combination with the Protocol Buffers maven plugin) which compiles a .proto file into a class. I can use the generated class in the default package perfectly, but not outside of it. I don't really know how to explain it any better so I'm going to show you some pictures.
I've tried subclassing the Hrp class but that doesn't work (the generated class is final). It is also not an option to move the class every time I re-generate the Hrp class.
I'm not sure if this is relevant, but the generated class is public final. It contains an empty, private constructor.
I have also tried setting the generated sources package prefix for the generated sources folder but that also does not work.
Any help would be greatly appreciated.
Try adding a package id to your Protocol Buffers definition. See Protocol Buffers Package
i.e.
syntax = "proto3";
package MyPackage;
option optimize_for = SPEED;
message Product {
repeated ASale sale = 1;
}
Then when you Generate the Java~Protocol~Buffers code (using protoc), it will be in package MyPackage and you will be able to import it into your java code in the normal way.
In java, you can not import anything from the Default package; which I believe is your problem. See How to access java-classes in the default-package?

Why is package-info.java useful?

When I run CheckStyle over my Java project it says Missing package-info.java file. for some classes, but not all of them. I can't really figure out why this message appears only sometimes. Furthermore my project runs perfectly fine without the package-info.java.
What does the package-info.java do? Do I really need it for my Java projects?
It is used to generate javadocs for a package.
/**
* Domain classes used to produce .....
* <p>
* These classes contain the ......
* </p>
*
* #since 1.0
* #author somebody
* #version 1.0
*/
package com.domain;
Will generate package info for com.domain package:
Example result: https://docs.oracle.com/javase/7/docs/api/java/awt/package-summary.html
Annotations
Another good reason to use package-info.java is to add default annotations for use by FindBugs. For instance, if you put this in your package-info file:
#DefaultAnnotation(NonNull.class)
package com.my.package;
then when findbugs runs on the code in that package, all methods and fields are assumed to be non-null unless you annotate them with #CheckForNull. This is much nicer and more foolproof than requiring developers to add #NonNull annotations to each method and field.
Not only some findbugs annotations, but a lot of java annotations in common libraries have the java.lang.annotation.ElementType.PACKAGE type as one of the possible values of their own java.lang.annotation.Target annotation, e.g.:
com.google.gwt.core.client.js.JsNamespace
com.querydsl.core.annotations.Config
com.sun.xml.bind.XmlAccessorFactory
groovy.transform.BaseScript
java.lang.Deprecated
javax.annotation.Generated
javax.xml.bind.annotation.XmlAccessorOrder
org.hibernate.annotations.TypeDef
net.sf.ehcache.pool.sizeof.annotations.IgnoreSizeOf
org.apache.hive.common.HiveVersionAnnotation
org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeAction
org.codehaus.commons.nullanalysis.NotNullByDefault
org.eclipse.persistence.oxm.annotations.XmlNameTransformer
org.glassfish.jersey.Beta
org.jgroups.annotations.Experimental
and much more.
This package-info.java file would be the file, where you can place such annotations (along with the javadoc).
A package-info.java file allows adding javadoc to document a whole package. See http://docs.oracle.com/javase/7/docs/api/java/applet/package-summary.html for example.
If you don't care about missing package documentation, then ignore the warning or disable the JavadocPackage check.
The package-info.java is a Java file that can be added to any Java source package. It is used to provide info at a "package" level as per its name.
It contains documentation and annotations used in the package.
javadoc example is already provided in the answer, the below part explains how it works incase of annotations.
For example, in the below file it is used to "substitute" the occurance of joda.time.DateTime with org.jadira.usertype.dateandtime.joda.PersistentDateTime
#TypeDefs({
#TypeDef(name = "PersistentDateTime", typeClass = PersistentDateTime.class, defaultForType=DateTime.class)})
package xyz.abc;
import org.hibernate.annotations.TypeDef;
import org.hibernate.annotations.TypeDefs;
import org.jadira.usertype.dateandtime.joda.PersistentDateTime;
import org.joda.time.DateTime;
There are a number of annotations available with which can be used to perform different things at "package" level. It can be found at https://docs.jboss.org/hibernate/orm/3.5/api/org/hibernate/annotations/package-summary.html

How to use ROME for RSS

I am using the code from Rome's tutorials page http://wiki.java.net/twiki/bin/view/Javawsxml/Rome05TutorialFeedReader .
When I try to compile, it says class FeedReader is public, should be declared in a file named FeedReader.java.
I am new to Java, but I think that the FeedReader class should be part of the package used in the example, or in one of the import paths. I can't find file com.sun.syndication.samples (which is the package from the example) in the Rome library I downloaded. Any thoughts?
The code from your tutorial is
package com.sun.syndication.samples;
public class FeedReader {
...
}
It must be in a file named FeedReader.java and put in a directory com/sun/syndication/samples. If you change the name of the class, you must change also the name of the java file. If you change the package declaration, you must also change the location of the file.

Categories