Friday, August 09, 2013

ProgrammaticMethodReference

Ever wanted to reference a method, but didn't want to use a fragile string literal and reflection to do it? I have, but couldn't find a library to do it, so I wrote this. It requires two method calls, but that's the only way I could figure to do it, while still handling void methods.

package ca.digitalrapids.lang;

import java.lang.reflect.Method;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;


/**
* Use like so:
* ProgrammaticMethodReference.putMethod(SomeClass.class).someMethod();
* Method method = ProgrammaticMethodReference.takeMethod();
*
* This class is thread safe.
*/
public class ProgrammaticMethodReference
{
static private ThreadLocal<Method> lastMethod = new ThreadLocal<Method>();

@SuppressWarnings("unchecked")
public static <T> T putMethod(Class<T> clazz)
{
return (T) createEnhancer(new MethodInterceptor()
{
@Override
public Object intercept(Object obj, Method method, Object[] args,
MethodProxy proxy) throws Throwable
{
lastMethod.set(method);
return null;
}
}, clazz).create();
}

private static Enhancer createEnhancer(MethodInterceptor interceptor, Class<?> clazz) {
Enhancer enhancer = new Enhancer();
enhancer.setCallback(interceptor);
enhancer.setSuperclass(clazz);
return enhancer;
}

public static Method takeMethod()
{
final Method result = lastMethod.get();
if ( result == null )
throw new RuntimeException("No method set. Call putMethod first.");
return result;
}
}

Thursday, October 18, 2012

Microsoft Visual Studio is Busy...Seriously?

It's 2012. Is your application really still compiling in the UI thread? Wow...

Friday, April 20, 2012

Proguard, Maven and obfuscating multiple layers of libraries


I was trying to obfuscate a project and kept getting errors like this:

[proguard] Warning: library class xxx.Xxx extends or implements program class yyy.YYY
 [proguard] Warning: there were 1 instances of library classes
depending on prog
ram classes.
 [proguard]          You must avoid such dependencies, since the
program classes
 will
 [proguard]          be processed, while the library classes will
remain unchang
ed.
 [proguard] Error: Please correct the above warnings first.

This can happen if you have a dependency to a library X, that transitively depends on library Y, and you try to obfuscate your app + Y, but not X.  There are two ways to solve this.

Solution 1:
Change X to obfuscate and embed Y. When your app is then obfuscated, it will have no idea that X references the classes in Y, as it will reference obfuscated versions.

Solution 2:
Change your app to obfuscate and embed X.  Thus X and Y will be obfuscated together, it will all be considered "app" code by Proguard, and the error will go away.

Thursday, April 19, 2012

Spring, JavaConfig and obfuscation

Obfuscation of Spring projects can be made easier by using Spring JavaConfig, as it mostly uses plain Java code, but there are some gotchas. Here are some tips (some might be proguard specific):
  • Don't use @Value to populate fields from system properties. Obfuscation can delete the annotation (even with proguard "-keepattributes *Annotation*"). Use this technique instead:

 static private final String someprop;
 static {
  /* @Value technique gets complicated w/ obfuscation */
  someprop = System.getProperty("somepropname");
 }

  • Don't use initMethod or destroyMethod attributes of @Bean. They use reflection, and reflection and obfuscation don't mix well. Do this instead:
 @Bean
 public SomeBean someBean()
 {
  SomeBean result = new SomeBean();
  /* destroyMethod attibute on @Bean gets messed up by obfuscation */
  Runtime.getRuntime().addShutdownHook(new Thread(new Runnable()
  {
   @Override
   public void run()
   {
    result.shutdown();
   }
  }));
  return result;
 }

  • Don't use -overloadaggressively. JavaConfig doesn't like @Bean methods with the same name.

  • Use -keepattributes *Annotation* to preserve annotations.

  • Preserve your public constructor if you reference your @Configuration class from Spring XML.  Also, keep all fields so they are autowired properly.
-keepclassmembers class com.mycompany.MyAppConfig { 
 <fields>;
 <init>();
}


  • Ensure your @Bean methods are obfuscated, but not shunk
-keepclassmembers,allowobfuscation class com.mycompany.MyAppConfig { 
 public *;
}
  • I had many problems when composing several @Configuration contexts like so:
@Configuration
@Import(Y.class)
public class X {
...
}

For some reason, X couldn't resolve some beans in Y. The only way I could get this to work was to change X to inherit from Y, like so:

@Configuration
public class X extends Y {
...
}

This, of course, imposes a hierarchical structure to your contexts.

Monday, June 13, 2011

Convert NetBeans IvyBeans project to Maven3

Add the following to nbproject/ivy-impl.xml to generate the pom.xml from the ivy.xml:
<ivy:makepom ivyfile="ivy.xml" pomfile="pom.xml">
<mapping conf="default" scope="compile"/>
<mapping conf="compile-test" scope="test"/>
<mapping conf="runtime" scope="runtime"/>
<mapping conf="runtime-test" scope="test"/>
</ivy:makepom>
Run it.

Create src/main/java and src/test/java. Add them to version control. SCM move source to src/main/java and test source to src/test/java.


Friday, May 27, 2011

Convert NetBeans 7.0 Ant-based app to Maven: Creating the dist

I recently converted a NetBeans 7.0 Ant-based application project to Maven. Here are the steps to create the dist dir.
  • Add the following to your pom.xml
<profiles>
<profile>
<id>release</id>
<activation>
<property>
<name>performRelease</name>
<value>true</value>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.3.1</version>
<configuration>
<archive>
<manifest>
<mainClass>your.MainClass</mainClass>
<addClasspath>true</addClasspath>
<classpathPrefix>lib/</classpathPrefix>
</manifest>
</archive>
</configuration>
</plugin>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<version>2.2</version>
<executions>
<execution>
<id>distro-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
<configuration>
<descriptors>
<descriptor>src/assemble/dist.xml</descriptor>
</descriptors>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
  • Create the file src/assemble/dist.xml with the following contents


<assembly
xmlns="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/plugins/maven-assembly-plugin/assembly/1.1.0 http://maven.apache.org/xsd/assembly-1.1.0.xsd">
<id>dist</id>
<formats>
<format>zip</format>
</formats>
<includeBaseDirectory>false</includeBaseDirectory>
<dependencySets>
<dependencySet>
<outputDirectory>lib</outputDirectory>
<useProjectArtifact>false</useProjectArtifact>
</dependencySet>
</dependencySets>
<fileSets>
<fileSet>
<directory>etc</directory>
<outputDirectory>.</outputDirectory>
</fileSet>
</fileSets>
<files>
<file>
<source>${project.build.directory}/${project.build.finalName}.jar</source>
<outputDirectory>.</outputDirectory>
<destName>YourAppName.jar</destName>
</file>
</files>
</assembly>
The etc dir is located at the root of your project, and contains anything else you want to put in your dist dir.

To create the distribution, run the following command

mvn -P release package

The result will be a jar in the target dir with a name like MyAppName-1.0-dist.jar.

Wednesday, March 23, 2011

Migrating NetBeans jar references to Ivy dependencies

Search on http://mvnrepository.com for the library

Sometimes mvnrepository doesn't have the library version you want. In this case I go to the library's website and see if they have a maven repository URL listed there. If they do, you have to add it to your ivysettings.xml file. If your NetBeans project doesn't yet have/use a ivysettings.xml file, you'll have to add one:
  • Add ivysettings.xml file to your project's root
  • Set the ivysettings.xml file in Project Properties/Ivy

<?xml version="1.0" encoding="UTF-8"?>

<ivysettings>
<settings defaultResolver="myChain"/>
<caches checkUpToDate="true"/>
<resolvers>
<chain name="myChain">
<ibiblio name="lombok" m2compatible="true"
root="http://projectlombok.org/mavenrepo/"/>
<ibiblio name="jetty" m2compatible="true"
root="http://oss.sonatype.org/content/groups/jetty/"/>
<ibiblio name="ibiblio" m2compatible="true"/>
</chain>
</resolvers>
</ivysettings>

Tip: Start by replacing the libraries that might have dependencies themselves, e.g., hibernate, and remove all the jar refs they bring in. This will avoid redundant dependency specification for common libraries, e.g., commons-logging. Also, you might find that you were importing jars you didn't even need. (In truth, though, you might get some new ones you don't need. The lib you are depending on might not have an Ivy configuration or Maven scope specific enough to your usage, in which case you could get some unnecessary jars.)

I had another problem where all my tests would be run twice, the second run crashing with no indication as to why. Turns out commons-jxpath 1.2 was bringing in ant-optional-1.5.1.jar, which screws up NetBeans test runs. Changing to reference jxpath 1.3 and deleting build/jar solved the problem.

Autodownloading Ivy & Cleaning build/jar

Being a fan of automation, I figured out how to autodownload Ivy and it's dependencies. Also, I discovered that IvyBeans calculates the classpath based on all the jars it finds in build/jar, but doesn't clean it, which can lead to problems (e.g., http://groups.google.com/group/usr-ivybeans/browse_thread/thread/629c30771ec8192d). So, I also added code to clean build/jar on every build. Add this to your NetBeans build.xml:



<!-- Ivy -->
<property name="ivy.install.version" value="2.2.0" />
<property name="ivy.home" value="${user.home}/.ant/lib" />
<property name="ivy.jar.file" value="${ivy.home}/ivy-${ivy.install.version}.jar" />
<property name="ant-contrib.version" value="1.0b3" />
<property name="ant-contrib.lib" value="${ivy.home}/ant-contrib-${ant-contrib.version}.jar" />
<target name="-pre-init" depends="download-ivy,clean-build-libs"/>
<taskdef resource="org/apache/ivy/ant/antlib.xml"
        uri="antlib:org.apache.ivy.ant" classpath="${ivy.jar.file}"/>
<target name="download-ivy">
<mkdir dir="${ivy.home}"/>
<get src="http://repo1.maven.org/maven2/org/apache/ivy/ivy/${ivy.install.version}/ivy-${ivy.install.version}.jar"
          dest="${ivy.jar.file}" usetimestamp="true" ignoreerrors="true" maxtime="15"/>
<get src="http://repo1.maven.org/maven2/ant-contrib/ant-contrib/${ant-contrib.version}/ant-contrib-${ant-contrib.version}.jar"
          dest="${ant-contrib.lib}" usetimestamp="true" ignoreerrors="true" maxtime="15"/>
</target>
<!-- Clean build libs so Ivy doesn't include stale jars in classpath -->
<target name="clean-build-libs">
<delete dir="${basedir}/${build.dir}/${lib.dir}"/>
</target>