Archive

Archive for the ‘Tecnologías’ Category

Cheap Virtual Reality option

Probably you have eared about the Virtual Reality, and how companies like Oculus Rift are creating devices for that.

I am very interested on it, because it could improve a lot your gaming experience. However, I was quite disappointed about the prize (around 700 euros in Europe) and the minimum requirements to use Oculus Rift. In their web page you can download one software that will check if your computer is acceptable or not.

I started to investigate if there is a possible option to use your smartphone as a screen, I even though to develop something, but finally it was not necessary. The main issue I found is that there are people comments from the middle of 2015 and I didn’t find almost nothing from that date to nowadays (it is 23-01-2016 in the moment that I’m writing these lines).

I’m going to show you how can we play Project Cars game using our smartphone as a Virtual Reality screen and head tracker.

What software do you need

  • Trinus VR. There is one server application for desktop and other for the client. Our client will be a Nexus 5 with Android 6.0.1. This application is going to redirect the image from your computer to your smartphone and it will be listening your inputs to do the head tracking. It also split the screen in two parts to provide the Virtual Reality effect when you insert your smartphone in a VR glasses.
  • Opentrack 2.3. As I said, Trinus VR will be listening your inputs… but wait, what inputs?. It could be a mouse move, UDP packages incoming, etc. Theoretically, we don’t need this application, but I realized that the head tracking in Project Cars doesn’t work with Trinus VR. So I will use Opentrack to fix this problem.
  • FreePIE IMU. There is no direct link to download. You will find this Android application inside the Opentrack 2.3. Instructions here. It is an APK that you will need to copy in your Android device and install it.

Physical devices

  • Android smartphone that can run Trinus VR client and FreePIE IMU.
  • PC that can run Project Cars and all the software mentioned with Windows 7 or higher.
  • USB cable to connect your smartphone with your computer.
  • (Optional) VR glasses to insert your smartphone.

Lets start

  • Install all the required software.
  • Plug your smartphone with USB to your PC.
  • Run Project Cars in windowed mode, because it is mandatory for the Trinus VR display. Start any circuit in free practice for example and set the camera to the helmet view.
  • PC: Run as an administrator, Trinus VR and Opentrack.
  • Android: Run Trinus VR and FreePIE IMU, use the IP of your computer.
  • Load the profile .ini (you will find it at the end of the post) in Opentrack and check the screenshots I attach.
  • Make sure that the port value is the same as FreePIE IMU is using. Click start and if everything is fine, the octopus will move with the movement of your smartphone.
  • Run Trinus VR with the configuration that you can see in the screenshots. It is important to set the Sensor output: Free Track.

At this point, you should see the game screen in your smartphone and the movements of the smartphone should be captured in the game.

One more thing. In my case, somehow Trinus VR starts to capture the inputs after clicking start in Opentrack and there is a mess because we have 2 programs setting the head track. There are two options:

  • Stop the Opentrack.
  • Set the sensor output in TrinusVR to no sensor.

Attachments

Screenshot_20160123-113157opentrack-port opentracktrinusVR-sensors trinusVR-net trinusVR-main

Profile .ini (Save this in a file with any name .ini and load it in the Opentrack as a profile)

[Curves-tx]
point-count=1
point-0-x=100
point-0-y=100

[opentrack-ui]
camera-pitch=0
camera-yaw=0
compensate-translation=true
compensate-translation-disable-z-axis=false
filter-dll=Accela
pitch-alt-axis-sign=false
pitch-invert-sign=false
pitch-source-index=4
pitch-zero-pos=0
protocol-dll=freetrack 2.0 Enhanced
roll-alt-axis-sign=false
roll-invert-sign=true
roll-source-index=5
roll-zero-pos=0
tracker-dll=FreePIE UDP receiver
tracker2-dll=
use-system-tray=false
x-alt-axis-sign=false
x-invert-sign=false
x-source-index=0
x-zero-pos=0
y-alt-axis-sign=false
y-invert-sign=false
y-source-index=1
y-zero-pos=0
yaw-alt-axis-sign=false
yaw-invert-sign=false
yaw-source-index=3
yaw-zero-pos=0
z-alt-axis-sign=false
z-invert-sign=false
z-source-index=2
z-zero-pos=0
button-center=-1
button-start-tracking=-1
button-stop-tracking=-1
button-toggle=-1
button-toggle-tracking=-1
button-zero=-1
camera-roll=0
center-at-startup=true
centering-method=1
guid-center=
guid-start-tracking=
guid-stop-tracking=
guid-toggle=
guid-toggle-tracking=
guid-zero=
keycode-center=F12
keycode-start-tracking=
keycode-stop-tracking=
keycode-toggle=
keycode-toggle-tracking=
keycode-zero=

[Curves-tx_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-ty]
point-count=1
point-0-x=100
point-0-y=100

[Curves-ty_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-tz]
point-count=1
point-0-x=100
point-0-y=100

[Curves-tz_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-rx]
point-count=3
point-0-x=7.1999998092651403
point-0-y=8.7804880142211896
point-1-x=30.034286499023398
point-1-y=90.439025878906193
point-2-x=50.194286346435497
point-2-y=180

[Curves-rx_alt]
point-count=1
point-0-x=180
point-0-y=180

[Curves-ry]
point-count=3
point-0-x=6.9942855834960902
point-0-y=8.7804880142211896
point-1-x=30.034286499023398
point-1-y=91.317070007324205
point-2-x=49.7828559875488
point-2-y=180

[Curves-ry_alt]
point-count=1
point-0-x=180
point-0-y=180

[Curves-rz]
point-count=1
point-0-x=180
point-0-y=180

[Curves-rz_alt]
point-count=1
point-0-x=180
point-0-y=33.365852355957003

[Accela]
ewma=2
rotation-threshold=25
translation-threshold=25

[freepie-udp-tracker]
add-pitch-degrees=0
add-roll-degrees=0
add-yaw-degrees=0
axis-index-x=0
axis-index-y=1
axis-index-z=2
port=5555

[keyboard-shortcuts]
key-alt-center=false
key-alt-toggle=false
key-alt-zero=false
key-ctrl-center=false
key-ctrl-toggle=false
key-ctrl-zero=false
key-index-center=19
key-index-toggle=0
key-index-zero=0
key-shift-center=false
key-shift-toggle=false
key-shift-zero=false

[proto-freetrack]
use-memory-hacks=false
used-interfaces=0

[udp-proto]
ip1=127
ip2=0
ip3=0
ip4=0
port=5556

This other profile works much better for me:

[Curves-tx]
point-count=1
point-0-x=100
point-0-y=100

[opentrack-ui]
camera-pitch=0
camera-yaw=0
compensate-translation=true
compensate-translation-disable-z-axis=true
filter-dll=Accela
pitch-alt-axis-sign=false
pitch-invert-sign=true
pitch-source-index=4
pitch-zero-pos=0
protocol-dll=freetrack 2.0 Enhanced
roll-alt-axis-sign=false
roll-invert-sign=true
roll-source-index=5
roll-zero-pos=0
tracker-dll=FreePIE UDP receiver
tracker2-dll=
use-system-tray=false
x-alt-axis-sign=false
x-invert-sign=true
x-source-index=0
x-zero-pos=0
y-alt-axis-sign=false
y-invert-sign=true
y-source-index=1
y-zero-pos=0
yaw-alt-axis-sign=false
yaw-invert-sign=false
yaw-source-index=3
yaw-zero-pos=0
z-alt-axis-sign=false
z-invert-sign=true
z-source-index=2
z-zero-pos=0
button-center=-1
button-start-tracking=-1
button-stop-tracking=-1
button-toggle=-1
button-toggle-tracking=-1
button-zero=-1
camera-roll=0
center-at-startup=true
centering-method=1
guid-center=
guid-start-tracking=
guid-stop-tracking=
guid-toggle=
guid-toggle-tracking=
guid-zero=
keycode-center=F11
keycode-start-tracking=
keycode-stop-tracking=
keycode-toggle=
keycode-toggle-tracking=
keycode-zero=

[Curves-tx_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-ty]
point-count=1
point-0-x=100
point-0-y=100

[Curves-ty_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-tz]
point-count=1
point-0-x=100
point-0-y=100

[Curves-tz_alt]
point-count=1
point-0-x=100
point-0-y=100

[Curves-rx]
point-count=3
point-0-x=7.1999998092651403
point-0-y=8.7804880142211896
point-1-x=76
point-1-y=80
point-2-x=180
point-2-y=180

[Curves-rx_alt]
point-count=1
point-0-x=180
point-0-y=180

[Curves-ry]
point-count=3
point-0-x=6.9942855834960902
point-0-y=8.7804880142211896
point-1-x=100
point-1-y=100
point-2-x=180
point-2-y=180

[Curves-ry_alt]
point-count=1
point-0-x=180
point-0-y=180

[Curves-rz]
point-count=1
point-0-x=180
point-0-y=180

[Curves-rz_alt]
point-count=1
point-0-x=180
point-0-y=33.365852355957003

[Accela]
ewma=2
rotation-threshold=25
translation-threshold=25

[freepie-udp-tracker]
add-pitch-degrees=0
add-roll-degrees=0
add-yaw-degrees=0
axis-index-x=0
axis-index-y=1
axis-index-z=2
port=5555

[keyboard-shortcuts]
key-alt-center=false
key-alt-toggle=false
key-alt-zero=false
key-ctrl-center=false
key-ctrl-toggle=false
key-ctrl-zero=false
key-index-center=19
key-index-toggle=0
key-index-zero=0
key-shift-center=false
key-shift-toggle=false
key-shift-zero=false

[proto-freetrack]
use-memory-hacks=false
used-interfaces=0

[udp-proto]
ip1=127
ip2=0
ip3=0
ip4=0
port=5556

Compile-time checking JPA queries

JPA provides several alternatives for querying data. Such alternatives may be classified attending to a variety of criteria, eg, language used (SQL vs JPQL) or whether queries are static (compilation time) or dynamic (execution time).

Static queries are defined using annotations @NamedQuery (javax.persistence.NamedQuery) and @NamedQueries (javax.persistence.NamedQueries) in the @Entity class definition itself:

 @NamedQuery(
            name="findAllCustomersWithName",
            query="SELECT c FROM Customer c WHERE c.name LIKE :custName"
    )

On the other hand, EntityManager provides methods createQuery(…) y createNativeQuery(…) which take either a JPQL or a SQL query, respectively.

Thus, queries can be defined both in compilation or execution time.

(Note: It is advisable to always use parametrized queries using methods setParameter(…) from Query to avoid SQL Injection vulnerabilities.

Criteria API

However, JPA provides an alternative approach to query objects: Criteria API. Indeed, one of the motivations to switch to JPA is to deal with objects rather than SQL dialects, isn’t it ?

Let’s look a sample code.

Entity definition:

@Entity
public class User {

 @Id
 private Integer userId;

 @Basic
 @Column(length=15, nullable=false)
 private String name;

 @Basic
 @Column(length=64, nullable=false)
 private String userDigestedPasswd;

 @Basic
 @Column(length=50, nullable=true)
 private String email;

 @Basic
 @Column(nullable=false)
 public Integer privilegeLevel;

 @Basic
 @Column(nullable=false)
 private Boolean active;
}

Let’s query db and check results (using JUnit):

public class UserTest {
 @Test
 public void testUserCriteria(){
EntityManagerFactory emf = null;
EntityManager em = null;
try {
  emf = Persistence.createEntityManagerFactory("criteria");
  em = emf.createEntityManager();
  final CriteriaBuilder cb = em.getCriteriaBuilder();
  final CriteriaQuery<User> q = cb.createQuery(User.class);
  final Root<User> users = q.from(User.class);
  final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);
  q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));
  em.getTransaction().begin();
  List<User> result = em.createQuery(q).getResultList();
  em.getTransaction().commit();

  assertNotNull(result);
  assertEquals(2, result.size());

  assertEquals(1, (int)result.get(0).getUserId());
  assertEquals("Pepe", result.get(0).getName());

  assertEquals(3, (int)result.get(1).getUserId());
  assertEquals("Dolores", result.get(1).getName());} catch (Exception e) {
  fail("Unexpected Exception " + e.getMessage());
} finally {
  if (em != null)
    em.close();
  if (emf != null)
    emf.close();
}
}
}

Following lines show query creation:

final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<User> q = cb.createQuery(User.class);
final Root<User> users = q.from(User.class);
final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);
q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));

First of all, get a CriteriaBuilder from an EntityManager. Then, get a CriteriaQuery instance, setting the class to hold results. In our case, User.class:

final CriteriaBuilder cb = em.getCriteriaBuilder();
final CriteriaQuery<User> q = cb.createQuery(User.class);

Following, the Entity to run the query against must be set:

final Root<User> users = q.from(User.class);

Now it’s time to set query matching conditions. In the sample code, the condition is just attribute privilegeLevel to be equals to 5:

final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);

Finally, query is built adding conditions on Root. Grouping and sorting options may be set too (ie, ascending sorting is set on userId):

q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));

Please have a look at CriteriaBuilder for different options. Grouping and sorting options may be found at CriteriaQuery.

Using metamodel for compile-time checking

Note the query we have just build requires to keep track of object attributes names. Eg, to build the query, the name of the attribute privilegeLevel is used. However, if attribute name were changed later, the code would compile and only fail at runtime:

final CriteriaQuery<User> q = cb.createQuery(User.class);
final Root<User> users = q.from(User.class);
final Predicate condition = cb.equal(users.get("privilegeLevel"), 5);
q.select(users).where(condition).orderBy(cb.asc(users.get("userId")));

That is no good.

Fortunately, using metamodel, we will be able to build compile-time checked queries. A brief introduction can be found at The Java EE6 Tutorial.

Using metamodel, the code will reference an SingularAttribute of the object rather than using a String holding the object attribute name. So, if object attribute were changed later, the compiler would flag it for us.

First of all, the correspondent metamodel class (EntityType) must be created. Although it can achieved by several ways, probably the easiest one, for openJPA implementation, is to add a openJPA build flag:  -Aopenjpa.metamodel=true.

So we have the class User_ created, which is the correspondent metamodel class for User:

* Generated by OpenJPA MetaModel Generator Tool. **/
package com.wordpress.tododev.criteria.entities;
import javax.persistence.metamodel.SingularAttribute;
@javax.persistence.metamodel.StaticMetamodel
(value=com.wordpress.tododev.criteria.entities.User.class)
@javax.annotation.Generated
(value="org.apache.openjpa.persistence.meta.AnnotationProcessor6",date="Mon Mar 04 16:47:46 CET 2013")
public class User_ {
 public static volatile SingularAttribute<User,Boolean> active;
 public static volatile SingularAttribute<User,String> email;
 public static volatile SingularAttribute<User,String> name;
 public static volatile SingularAttribute<User,Integer> privilegeLevel;
 public static volatile SingularAttribute<User,String> userDigestedPasswd;
 public static volatile SingularAttribute<User,Integer> userId;
}

If such class were added to code repo, any later change to class User would remain unnoticeable. Moreover, it is not a good idea to add auto-generated items to code versioning systems.

Using ant, maven or similar tools, a target could be added to create metamodel classes. Such target should be executed after any change to JPA Entities.

Also possible to use IDE for that. Eg, for those using Eclipse, just need to add the already mentioned compilation flag to Properties->Java Compiler->Annotation Processor and the lib (jar) containing the Annotation Processor for the chosen JPA implementation to section Factory Path within Annotations Processor (could lead to compilation issues in auto mode, provided that metamodel class must be compiled before the code using it).

Let us add another test to the suite. This one will not provide a String containing the attribute name, but use the metamodel class instead:

@Test
 public void testUserCriteriaMetaModel(){
 EntityManagerFactory emf = null;
 EntityManager em = null;
 try {
 emf = Persistence.createEntityManagerFactory("criteria");
 em = emf.createEntityManager();
 final CriteriaBuilder cb = em.getCriteriaBuilder();
 final CriteriaQuery<User> q = cb.createQuery(User.class);
 final Metamodel m = em.getMetamodel();
 final Root<User> user = q.from(m.entity(User.class));
 final Predicate condition = cb.equal(user.get(User_.privilegeLevel), 5);
 q.select(user).where(condition).orderBy(cb.asc(user.get(User_.userId)));

 em.getTransaction().begin();
 List<User> result = em.createQuery(q).getResultList();
 em.getTransaction().commit();

 assertNotNull(result);
 assertEquals(2, result.size());

 assertEquals(1, (int)result.get(0).getUserId());
 assertEquals("Pepe", result.get(0).getName());

 assertEquals(3, (int)result.get(1).getUserId());
 assertEquals("Dolores", result.get(1).getName());
} catch (Exception e) {
 fail("Unexpected Exception " + e.getMessage());
 } finally {
 if (em != null)
 em.close();
 if (emf != null)
 emf.close();
 }
 }

More relevant changes are user.get(User_.privilegeLevel) instead of users.get(“privilegeLevel”) and  user.get(User_.userId) instead of  users.get(“userId”).

Download source code from GitHub

Playing with JerseyTest (Jersey 2.5.1 and DI)

I’m going to try explaining a trivial REST example. The idea is building a basic schema to start playing with Jersey. When I begin to use some framework, I usually develop a test enviroment for failing fast, and that is what I’m going to do.

The next example has these features:

  • Jersey 2.5.1
  • Dependency Injection
  • JUnit for testing

Classes:

  • Resource: it will attend the HTTP calls.
  • Service: it’s an interface with two implementations, Impl1 and Impl2.
  • ServiceProvider: it will give the apropiate implementation of Service per each request call in runtime.
  • TestBinder: it set the bindings into the Resource.

 


import static org.junit.Assert.assertEquals;

import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Application;
import javax.ws.rs.core.Response;

import org.glassfish.hk2.api.Factory;
import org.glassfish.hk2.utilities.binding.AbstractBinder;
import org.glassfish.jersey.process.internal.RequestScoped;
import org.glassfish.jersey.server.ResourceConfig;
import org.glassfish.jersey.test.JerseyTest;
import org.junit.Test;

public class JerseyInjectionTest extends JerseyTest {

	private static final String EXPECTED_CONTENT = "any string :P";

	/**
	 * Checks that the Resource uses Impl1.class
	 */
	@Test
	public void invokeImpl1(){
		invoke(Impl1.class);
	}
	
	/**
	 * Checks that the Resource uses Impl2.class
	 */
	@Test
	public void invokeImpl2(){
		invoke(Impl2.class);
	}
	
	/**
	 * Checks that Resource.anyContent has always the value of EXPECTED_CONTENT
	 */
	@Test
	public void checkContent(){
		Response response = target("example/content").request().get();
		assertEquals(EXPECTED_CONTENT, response.readEntity(String.class));
	}
	
	private <T extends Service> void invoke(Class<T> service){
		final String serviceName = service.getName();
		Response response = target("example/"+serviceName).request().get();
		assertEquals(service.getName(), response.readEntity(String.class));
	}
	
	/**
	 * Register the Resource and TestBinder in the Application
	 */
	@Override
	protected Application configure() {
		return new ResourceConfig() {
			{
				register(new TestBinder());
				register(Resource.class);
			}
		};
	}

	@Path("/example")
	public static class Resource {

		@Inject
		Service service;
		@Inject
		String anyContent;

		/**
		 * Returns the name of the Service's implementation
		 */
		@GET
		@Path("/{serviceClass}")
		public Response getDynamicInvokedService() {
			return Response.ok(service.getClass().getName()).build();
		}

		/**
		 * Returns always the value of anyContent
		 */
		@GET
		@Path("/content")
		public Response getStaticContent() {
			return Response.ok(anyContent).build();
		}

	}
	
	/**
	 * This class will help Resource to set the @Inject fields.
	 */
	public static class TestBinder extends AbstractBinder{

		@Override
		protected void configure() {
			bindFactory(ServiceProvider.class).to(Service.class);
			bind(EXPECTED_CONTENT).to(String.class);
		}
		
	}

	/**
	 * This class will instance a Services's implementation
	 * per each time that the Resource is called.
	 */
	@RequestScoped
	public static class ServiceProvider implements Factory<Service> {

		private final String serviceName;

		public ServiceProvider(@PathParam("serviceClass") String serviceName) {
			this.serviceName = serviceName;
		}

		@Override
		public void dispose(Service arg0) {}

		@Override
		public Service provide() {
			try {
				return (Service) Class.forName(serviceName).newInstance();
			} catch (Exception e) {
				return null;
			}
		}

	}

	/**
	 * Dummy services
	 */
	public static interface Service {}
	public static class Impl1 implements Service {}
	public static class Impl2 implements Service {}

}

Now we can try new features easily.

I hope that helps.

Actualizar a JDK7 en Linux sin romper /etc/alternatives

Como seguramente ya sabéis, debido a temas relacionados con licencias, el JDK7 de Sun (digo, Oracle) no estará incluido en los repositorios de Debian o Ubuntu. Esto no quiere decir que no se pueda instalar JDK7 en Linux, ya que puede descargarse de Sun en forma de .tgz e instalarlo.

Normalmente la instalación de los JDK se realizaba mediante el correspondiente paquete .deb. Dichos paquetes, además de instalar el JDK en sí, realizaban otra serie de operaciones, entre las que se encuentran actualizar ciertos componentes del sistema. Entre dichos componentes se encuentra /etc/alternatives, que consiste en el mantenimiento de una serie de enlaces simbólicos que permiten, en este caso, usar las herramientas del nuevo JDK sin tener que modificar los ejecutables o paths ya configurados en otras aplicaciones.

Sin embargo, al no instalarse el nuevo JDK como un paquete .deb, tendremos que actualizar el sistema  /etc/alternatives a mano.

En primer lugar, veamos el estado actual del sistema:

yo@localhost:~$ update-alternatives --list java
/usr/lib/jvm/java-6-openjdk/jre/bin/java
/usr/lib/jvm/java-6-sun/jre/bin/java

En este caso vemos las alternativas para el comando java, que consisten en la proporcionada por OpenJDK y la máquina de Sun en su versión 6. Sin embargo, el JDK no es sólo java, contiene una completa serie de utilidades. Sin  ir mas lejos, para compilar, necesitaremos javac. Veamos:

yo@localhost:~$ update-alternatives --list javac
/usr/lib/jvm/java-6-openjdk/bin/javac
/usr/lib/jvm/java-6-sun/bin/javac

Además podemos ver cuál es la versión que se usará por defecto. Por ejemplo, para javac obtenemos:

yo@localhost:~$ update-alternatives --display javac
javac - modo manual
el enlace apunta actualmente a /usr/lib/jvm/java-6-sun/bin/javac
/usr/lib/jvm/java-6-openjdk/bin/javac - prioridad 1061
esclavo javac.1.gz: /usr/lib/jvm/java-6-openjdk/man/man1/javac.1.gz
/usr/lib/jvm/java-6-sun/bin/javac - prioridad 1062
esclavo javac.1.gz: /usr/lib/jvm/java-6-sun/man/man1/javac.1.gz
Actualmente la «mejor» versión es /usr/lib/jvm/java-6-sun/bin/java.

La salida indica que el javac por defecto es el que nos proporciona el JDK6.

Como es de esperar, el sistema /etc/alternatives proporciona un interfaz para añadir nuevas alternativas. Sin embargo, un paquete como los JDK requiere actualizar muchas “alternativas” y sus “esclavas” de páginas de documentación.

Para actualizar varias máquinas, o si no quieres complicarte la vida usando /etc/alternatives directamente, puedes usar el script que proporcionamos a continuación. Se le pasa como parámetro el directorio donde has instalado el JDK7 (sin la / del final, es un poco tosco, la verdad) y genera un archivo update-alternatives, para añadir las nuevas opciones, y otro set-alternative, que las pone como valor por defecto. Echando un vistazo a estos archivos puedes hacerte una idea de la cantidad de “alternativas” que se actualizarán.

Después de ejecutarlo tu sistema debería haberse actualizado de la siguiente manera:

yo@localhost:~$ update-alternatives --list javac
/usr/lib/jvm/java-6-openjdk/bin/javac
/usr/lib/jvm/java-6-sun/bin/javac
/usr/lib/jvm/jdk1.7.0_03/bin/javac

yo@localhost:~$ update-alternatives --display javac
javac - modo manual
el enlace apunta actualmente a /usr/lib/jvm/jdk1.7.0_03/bin/javac
/usr/lib/jvm/java-6-openjdk/bin/javac - prioridad 1061
esclavo javac.1.gz: /usr/lib/jvm/java-6-openjdk/man/man1/javac.1.gz
/usr/lib/jvm/java-6-sun/bin/javac - prioridad 1062
esclavo javac.1.gz: /usr/lib/jvm/java-6-sun/man/man1/javac.1.gz
/usr/lib/jvm/jdk1.7.0_03/bin/javac - prioridad 1063
esclavo javac.1: /usr/lib/jvm/jdk1.7.0_03/man/man1/javac.1
Actualmente la «mejor» versión es /usr/lib/jvm/jdk1.7.0_03/bin/javac.

Sin mas dilación, el script. Ni que decir tiene que, como es habitual, si lo usas es por tu cuenta y riesgo, etc… En definitiva, que no nos culpes si algo sale mal 😉

#!/bin/bash

NEWDIR=$1; # without final slash please !!
LATEST=$((`update-alternatives --query java|grep Priority:|awk '{print $2}'|sort -n|tail -1`+1));

echo "Adding JDK from $1 with priority $LATEST";

BASH_FILE="update-alternatives";
UPDATE_FILE="set-alternative";

echo "#!/bin/bash" > $BASH_FILE;
echo "####" >> $BASH_FILE;
echo "####" >> $BASH_FILE;

echo "#!/bin/bash" > $UPDATE_FILE;
echo "####" >> $UPDATE_FILE;
echo "####" >> $UPDATE_FILE;
# man update-alternatives
# update-alternatives --install link name path priority [--slave link name path]...

for f in /etc/alternatives/*
do
fname=`echo $f | awk -F'/' '{print $4}' `;

CMD="update-alternatives --install /usr/bin/";
BIN="bin/$fname";
MAN="man/man1/${fname}.1";
MANGZ="man/man1/${fname}.1.gz";

if [ -f $NEWDIR/$BIN ] ; then
echo "# Adding $fname" >> $BASH_FILE;
CMD="${CMD}${fname} ${fname} $NEWDIR/$BIN $LATEST ";

echo "update-alternatives --set ${fname} $NEWDIR/$BIN">> $UPDATE_FILE;

if [ -f $NEWDIR/$MAN ] ; then
echo "# Adding slave ${fname}.1" >> $BASH_FILE;
CMD="${CMD} --slave /usr/share/$MAN ${fname}.1 $NEWDIR/$MAN";
fi

if [ -f $NEWDIR/$MANGZ ] ; then
echo "# Adding slave ${fname}.1.gz" >> $BASH_FILE;
CMD="${CMD} --slave /usr/share/$MANGZ ${fname}.1.gz $NEWDIR/$MANGZ";
fi
echo "$CMD" >> $BASH_FILE;
fi
done

exit;