Occasionally a slight modification to a Java source file like some additional explicit casts to help the compiler can improve compile time from 4 minutes to 3 seconds for a single java file (Especially in Java 8).
The problem is: In a large java project, how do you find which particular .java files are compiling slowly?
Is there a way to get Ant to time how long it takes to compile each individual .java file?
I think that this might be possible. Here's what I've found:
If you're using Java 8, you can register a Plugin with the compiler to add some additional functionality during compilation. The documentation has this to say about plugins:
It is expected that a typical plug-in will simply register a TaskListener to be informed of events during the execution of the compilation, and that the rest of the work will be done by the task listener.
So you can setup a plugin to use a TaskListener, and have the task listener log timestamps when class are being generated.
package xyz;
import com.sun.source.util.JavacTask;
import com.sun.source.util.Plugin;
public class TimestampPlugin implements Plugin {
#Override
public String getName() {
return "Timestamp_Plugin";
}
#Override
public void init(JavacTask task, String... strings) {
task.setTaskListener(new FileTimestampListener());
}
}
Documentation for TaskListener. A task listener is passed a TaskEvent, which has a Kind. In your case it sounds like you're interested in generation.
package xyz;
import com.sun.source.util.TaskEvent;
import com.sun.source.util.TaskListener;
import java.util.HashMap;
public class FileTimestampListener implements TaskListener {
HashMap<String, Long> timeStampMap = new HashMap<>();
#Override
public void started(TaskEvent taskEvent) {
if(TaskEvent.Kind.GENERATE.equals(taskEvent.getKind())) {
String name = taskEvent.getSourceFile().getName();
timeStampMap.put(name, System.currentTimeMillis());
}
}
#Override
public void finished(TaskEvent taskEvent) {
if(TaskEvent.Kind.GENERATE.equals(taskEvent.getKind())) {
String name = taskEvent.getSourceFile().getName();
System.out.println("Generated " + name + " over " + (System.currentTimeMillis() - timeStampMap.get(name)) + " milliseconds");
}
}
}
This is a simple example but it should be straightforward from here to set up something like a log file to store the information gathered. As you can see in the plugin's init function, arguments can be passed to the Plugin from the command line.
The plugin is configured by specifying it with the -Xplugin compiler argument. I'm not sure why but there doesn't appear to be any documentation on this page about it, but it can used by setting up a file called com.sun.source.util.Plugin (the FQ class name of the interface to implement) in your META-INF/services directory. So:
META-INF
|-- services
|-- com.sun.source.util.Plugin
And in that file list the FQ class name of your implementation of this class. So the file contents would be:
xyz.TimestampPlugin
In your Ant task you'll just need to specify a compiler flag -Xplugin:Timestamp_Plugin (note this is the name provided by the Plugin's getName() function). You'll also need to provide the compiled Plugin and runtime dependencies on the classpath, or the annotation processor path, if one is specified.
Related
I'm creating a Gradle plugin with its corresponding objects for use in the Groovy DSL. I'm confused between the difference and extension and a task and how configuration should be passed between the two along with where the input and out annotations should be put. Here's my task
abstract public class UrlVerify extends DefaultTask {
#Input
abstract public Property<String> getUrl();
#TaskAction
public void verify() {
System.out.println(getUrl().get().toString());
}
}
Here's the extension
abstract public class UrlVerifierExtension {
abstract public Property<String> getUrl();
abstract public Property<Configuration> getConfiguration();
abstract public Property<Boolean> getIgnoreFailures();
public Set<ConflictCategory> getIncludeCategories() {
return includeCategories;
}
}
This plugin simply accepts a URL and validates it.
verification {
url = 'https://www.moooooereee.com/'
configuration = configurations.runtimeClasspath
ignoreFailures = false
}
I have the following plugin. I manually needed to pass the URL from the extension to the task and wondered if this is the correct way?
public class UrlVerifierPlugin implements Plugin<Project> {
#Override
public void apply(Project project) {
project.getPluginManager().apply(JavaLibraryPlugin.class);
UrlVerifierExtension extension = project.getExtensions().create("verification", UrlVerifierExtension.class);
UrlVerify verifyUrlTask = project.getTasks().create("verifyUrl", UrlVerify.class);
verifyUrlTask.getUrl().set(extension.getUrl());
}
}
Along with this, it is also unclear whether the #Input annotation belongs to the properties of the extension or the task?
You seem to have followed the examples from the Gradle documentation very precisely. This is the correct way to configure your custom tasks. The exact purpose of extensions is to have user-provided settings which are then consumed by your plugin to configure it and tasks.
Extensions are for the user to provide settings.
Tasks are for executing an action while Gradle is running.
The #Input annotation is used by Gradle to determine if the tasks needs to run. If the tasks has not run before, or if the input value has changed since the previous execution, then the tasks will run again.
Outputs declare some result produced by running the task. An example is a task that compiles Java files. The outputs would the the class files produced from the compilation process. If output files are modified or deleted by something other than the task that created them, then the task that created them is out-of-date, and Gradle will run it again.
Also, a task can declare the outputs of another task as its input. If task A creates some output files, and task B uses the outputs of task A as an input, then task B will be run when task A updates or creates its files.
In your case with the #Input annotation, my guess is that you do not want that in this case, because it tells Gradle that your tasks only needs to run once, then after that, only if the user updates the setting.
I'm aware that Intellij can scan for this - and there seems to be an ancient project called Scannotation. Neither of these seems to do what I want.
I want to list the full package names of classes in my codebase that are marked #Deprecated.
My question is: How to list all deprecated classes to a file?
You can use Structural Search.
CTRL + SHIFT + A --> Search Structurally
Use this to find all deprecated classes:
#Deprecated
class $class$ {
}
On a unix/linux system you could use find . -name *.java -regex "#Deprecated"
This would give a list of files with absolute paths that could be changed to package notation with sed or any other text editor.
On Windows you might consider installing git which brings a bash where you can run this command.
This is what you can use:
package util;
import io.github.lukehutch.fastclasspathscanner.FastClasspathScanner;
import io.github.lukehutch.fastclasspathscanner.matchprocessor.ClassAnnotationMatchProcessor;
public class AnnotationScanTest {
public static void main(String args[]) throws ClassNotFoundException {
new FastClasspathScanner("com")
// Optional, in case you want to debug any issues with scanning.
// Add this right after the constructor to maximize the amount of log info.
.verbose()
// Add a MatchProcessor ("Mechanism 1")
.matchClassesWithAnnotation(Deprecated.class, new ClassAnnotationMatchProcessor() {
#Override
public void processMatch(Class<?> matchingClass) {
System.out.println("Matching annotation: " + matchingClass);
}
})
// Actually perform the scan (nothing will happen without this call)
.scan();
}
}
I would like to define a version number in a main class in each jar file that is assigned at compile time, like what can be easily done in C with an #include statement with a value from an external file. I would like to only set a value in that external location once, so any jar files that get compiled/built until I change it gets that same value.
My first thought was to define it in a common class then simply reference it like this:
I create a Base.java file:
class Base
{
public final static String version = "1.2.3";
}
Then I compile Base.java and jar it up.
And then I create a Module1.java file:
class Module1
{
public final static String version = Base.version;
public static void main( String[] args )
{
Module1();
}
Module1()
{
System.out.println( "Module1: "+this.version );
}
}
But of course, this won't compile without importing Base class, so I insert this just before the Module1 class:
import Base;
And I compile Module1.java and jar it up, and execute it; and as expected it returns:
Module1: 1.2.3
So far so good. But then I edit the Base.java file and change the version value to something different, like, say, "1.3.0", then compile Base.java and jar it up.
And now I want to create a Module2.java file:
import Base;
class Module2
{
public final static String version = Base.version;
public static void main( String[] args )
{
Module2();
}
Module2()
{
System.out.println( "Module2: "+this.version );
}
}
And I compile and jar up Module2, and execute it it correctly returns:
Module2: 1.3.0
Also good. But as a sanity check I expect (want/hope) Module1 to return the same results as before, so I rerun Module1, but Bogus! It returns:
Module1: 1.3.0
Any advice on how to pull this off? So the version in a module remains as it was at compile-time, not set during each session at run-time?
In Java, the standard place for storing the version of a .jar file is the manifest file (META-INF/MANIFEST.MF), not a class file. Specifically, put this line there:
Implementation-Version: 1.2.3
See here for more details.
To access this information from your code, use the java.util.jar.Manifest class, and specifically the getMainAttributes() method.
I need to have a jar file located in a main/assets directory within an Android project. It is important the jar file is located there.
With my main Android project is there a way to reference this jar file in my code and to use its classes?
To be clear I don't want to add the jar to the main project once compiled.
EDIT: I have tried the link below and it seems to load the Class file I've stated. But I'm strugging how to define constructor arguments for the dynamically loaded Class.
android-custom-class-loading-sample
EDIT2
Nearly there. I've confirmed the class is loaded from my classes.jar. I'm stuck instantiating it though.
On the licenseValidatorClazz.getConstructor line I get the error below. I'm guessing I'm missing something from my Interface file?
java.lang.NoSuchMethodException: [interface com.google.android.vending.licensing.Policy, interface com.google.android.vending.licensing.DeviceLimiter, interface com.google.android.vending.licensing.LicenseCheckerCallback, int, class java.lang.String, class java.lang.String]
public Class licenseValidatorClazz = null;
public LicenseValidator validator;
...
// Initialize the class loader with the secondary dex file.
DexClassLoader cl = new DexClassLoader(dexInternalStoragePath.getAbsolutePath(),
optimizedDexOutputPath.getAbsolutePath(),
null,
mContext.getClassLoader());
try {
// Load the library class from the class loader.
licenseValidatorClazz = cl.loadClass("com.google.android.vending.licensing.LicenseValidator");
validator = (LicenseValidator) licenseValidatorClazz.getConstructor(Policy.class,DeviceLimiter.class,LicenseCheckerCallback.class,int.class,String.class,String.class).newInstance(ddd, new NullDeviceLimiter(),
callback, generateNonce(), mPackageName, mVersionCode);
} catch (Exception exception) {
// Handle exception gracefully here.
exception.printStackTrace();
}
I have an Interface which contains the functions to pass to the loaded class.
public interface LicenseValidator
{
public LicenseCheckerCallback getCallback();
public int getNonce();
public String getPackageName();
public void verify(PublicKey publicKey, int responseCode, String signedData, String signature);
public void handleResponse(int response, ResponseData rawData);
public void handleApplicationError(int code);
public void handleInvalidResponse();
}
TO use an external jar to be associated with your application and use it during runtime, it needs to be in dalvik format since normal jars cannot work under dalvikVM.
Convert your files using the dx tool
using aapt cmd , add those classes.dex to your jar file.
Now this jar which contains files in dalvik format can be loaded into our project.
Here is a post which explains the procedure to accomplish it.
There are steps to accomplish this.
You have to make a copy of your JAR file into the private internal storage of your aplication.
Using the dx tool inside the android folder, you have to generate a classes.dex file associated with the JAR file. The dx tool will be at the location /android-sdks/build-tools/19.0.1 (this file is needed by the Dalvik VM, simply jar can not be read by the dalvik VM))
Using the aapt tool command which is also inside the same location, you have to add the classes.dex to the JAR file.
This JAR file could be loaded dynamically using DexClassLoader.
If you are making a JAR from any one your own library, you have to do this steps (1-4) every time when there is a change in your library source code. So you can automate this steps by creating a shell script(in Mac/Linux/Ubuntu) or batch scripts(in Windows). You can refere this link to understand how to write shell scripts.
Note : One situation for implementing this method is, when it is impossible to add the JAR files directly to the build path of core project and need to be loaded dynamically at run time. In normal cases the JAR files could be added to the build path.
please check this link for the detailed code and implementation.
How to load a jar file at runtime
Android: How to dynamically load classes from a JAR file?
Hope this helps!!
You should try out the Services API - java.util.ServiceLoader
You define a service interface and its implementations in your jar.
package com.my.project;
public interface MyService { ... }
public class MyServiceBarImpl implements MyService { ... }
public class MyServiceFooImpl implements MyService { ... }
Then you define the services contained within the jar file in the META-INF/services/ directory. For instance, in the file 'META-INF/services/com.my.project.MyService', you list the provider classes.
# Known MyService providers.
com.my.project.MyServiceBarImpl # The original implementation for handling "bar"s.
com.my.project.MyServiceFooImpl # A later implementation for "foo"s.
Then, in your main codebase, you can instantiate a MyService instance with the ServiceLoader:
for (MyService service : ServiceLoader.load(MyService.class)) {
//Perform some test to determine which is the right MyServiceImpl
//and then do something with the MyService instance
}
These examples are taken more-or-less straight from the API, although I've changed the package names to make them slightly less annoying to read.
I thought I would use the new ResourceBundleControlProvider framework in Java 8 to fix something which Oracle themselves will never fix - the default encoding used when reading resource bundles.
So I made a control:
package com.acme.resources;
import java.io.IOException;
import java.util.Locale;
import java.util.ResourceBundle;
public class AcmeResourceBundleControl extends ResourceBundle.Control
{
#Override
public ResourceBundle newBundle(String baseName, Locale locale, String format,
ClassLoader loader, boolean reload)
throws IllegalAccessException, InstantiationException, IOException
{
throw new UnsupportedOperationException("TODO");
}
}
Then I made a provider:
package com.acme.resources;
import java.util.ResourceBundle;
import java.util.spi.ResourceBundleControlProvider;
public class AcmeResourceBundleControlProvider implements ResourceBundleControlProvider
{
private static final ResourceBundle.Control CONTROL = new AcmeResourceBundleControl();
#Override
public ResourceBundle.Control getControl(String baseName)
{
if (baseName.startsWith("com.acme."))
{
return CONTROL;
}
else
{
return null;
}
}
}
Then in META-INF/services/java.util.spi.ResourceBundleControlProvider:
com.acme.resources.AcmeResourceBundleControlProvider
Then I just tried to run our application from IDEA and I find that it never loads my provider (otherwise the exception would be raised.)
I have checked the names and they all seem to match up. I have checked the compiler output directory IDEA is using and it does contain the service file. I wrote a simple test program which just tries to look up the service:
public static void main(String[] args)
{
for (ResourceBundleControlProvider provider :
ServiceLoader.load(ResourceBundleControlProvider.class))
{
System.out.println(provider.getClass());
}
}
This does print out one entry which is the name of my implementation class. So the issue is not in the service file.
If I breakpoint inside ResourceBundle, I seem to be able to access the custom provider class. Initial forays into the debugger show that ServiceLoader isn't finding any implementations, but I can't figure out why. I'm sure there is some dodgy class loader magic going on which results in not loading my class. :(
Some scary documentation on the Javadoc makes it sound like it might have to be installed as a global extension. If that really is the case, it's a bit of a shame, because it seemed like a useful way to override the default (and in my opinion broken) behaviour. But I also read the tutorial on the matter and it didn't seem to be describing anything like that (unless the good behaviour was pulled out of Java 8 at the very last minute and the docs are out of date!)
The tutorial does state that the JAR containing the ResourceBundleControlProvider must be in the JVM's system extension directory. Section 6 of the tutorial describes the requirement:
java -Djava.ext.dirs=lib -cp build RBCPTest
When you install a Java extension, you typically put the JAR file of the extension in the lib/ext directory of your JRE. However, this command specifies the directory that contains Java extensions with the system property java.ext.dirs.
The JavaDoc for ServiceLoader.loadInstalled() also states that providers on the application's class path are ignored.
Your problem is that the java.util.ResourceBundle that comes with the JVM does a ServiceLoader.loadInstalled(ResourceBundleControlProvider.class) to obtain a list of providers in the static initializer, and uses the thus obtained list ever after.