Deprecated Deprecation

JDK9 includes many exciting changes. It’s easy to overlook the minor ones, when they have to fight for the spotlight with features like the module system or the jshell. One of those features is JEP 277: Enhanced Deprecation. Yeah, i know… deprecation. Not exactly a word we like to see in our precious codebase. But necessary none the less, so here we go!

Why Deprecation?

So, before jumping right into the changes introduced with JEP 277, lets take a moment to reflect on the reasons Deprecation was introduced back in JDK2. Back then, no possibility existed to mark unsatisfactory code as such. Therefore everybody was forced to rely on their own jurisdiction regarding the usability of foreign code.

It’s easy to forget how important the information of deprecation is, especially for our current development paradigms. Not only are we notified about significant changes of modules we use, it also enables us to provide this same information to others who use our code. This is an essential communication feature, but also easily misused or ignored entirely. JEP 277 was introduced to tackle these problems and provide a solution to the rather neglecting treatment deprecation received so far.

The changes

So what exactly was changed with JEP 277? Well, the most significant changes were done to the Annotation itself. Two new attributes were introduced to enable developers to provide information about the current state of the depcrecation more precise. The first added attribute is „since“ and contains a String. It is used to indicate the age of the information itself. Authors are able to precisely document the version number, in which the Source went into deprecation for the first time. This can be helpful to pinpoint the reasons for the deprecation as well as alternatives for the deprecated source even in later versions, as they should be mentioned in the documentation of the respective version.

The second added attribute is „forRemoval“ and is depicted by a simple Bool with „false“ as it’s default. The provided information is just as simple as it is crucial. If the value is „true“, the annotated API element is marked for future removal. This means as well, that future versions *will* break code which uses this element. It is intended to emphasize the necessity of refactoring any code using those elements as well as providing authors an undisputable tool to remove unwanted elements from their code. Please take note though, that the flag only indicates *that* the deprecated element will be removed, not *why*. This was decided to prevent bloating the annotation too much and should therefore be documented in the javadoc tag.

The behaviour

Of course having those new attributes is already a nice asset, but it would be even better if they were recognised and covered by the JVM. Well, don’t fret, JEP 277 covered this as well! The biggest impact affects, as to be expected, the warning policy. The „forRemoval“-Tag introduces an entirely new type of deprecation, also requiring a heavier
handling than normal deprecation. In JEP 277 this is coined with the term „terminal deprecation“. The warning policy is affected by the deprecation state of both provider and user. Before the introduction of terminal deprecation, this of course meant that only four different states could be achieved. Terminal deprecation pushes the number of possible states up to nine. Tab 1.1 and 1.2 show the corresponding differences. As we can see, ordinary deprecation only issued a warning when the users site was not deprecated as well. Terminal deprecation on the other hand issues a warning at all possible states and got it’s own harsher formulated „terminal warning“. In order to visualize this, we will now create some sourcecode to show the difference in behaviour at compile time. First of all, we create two modules in order to simulate the integration of a foreign framework into our own code:

module Deprecated.Examples {
    exports org.normal;
    exports org.deprecated.directly;
    exports org.deprecated.indirectly;
}

module Deprecated.Changes {
    requires Deprecated.Examples;
}

Then we create a simple class to call a number of deprecated classes and methods from our second module, which itself
is not deprecated:

package jdk9.deprecated;

import org.deprecated.directly.DirectlyDeprecatedClass;
import org.deprecated.directly.DirectlyDeprecatedClassForRemoval;
import org.deprecated.directly.DirectlyDeprecatedMethods;
import org.deprecated.directly.DirectlyDeprecatedMethodsForRemoval;
import org.deprecated.indirectly.IndirectlyDeprecatedMethods;
import org.deprecated.indirectly.IndirectlyDeprecatedMethodsForRemoval;

/**
 * Shows the warnings concerning Deprecation in JDK9
 * Created by Simon on 11.05.2017.
 */
public class DeprecationTester {
    public static void main(String[] args) {
        DirectlyDeprecatedClass showsDeprecation = new DirectlyDeprecatedClass();
        DirectlyDeprecatedClassForRemoval showsDeprecationToo = new DirectlyDeprecatedClassForRemoval();
        DirectlyDeprecatedMethods directlyDeprecatedMethods = new DirectlyDeprecatedMethods();
        DirectlyDeprecatedMethodsForRemoval directlyDeprecatedMethodsForRemoval = new DirectlyDeprecatedMethodsForRemoval();
        directlyDeprecatedMethods.doDeprecatedThings();
        directlyDeprecatedMethodsForRemoval.doReallyDeprecatedThings();
        IndirectlyDeprecatedMethods indirectlyDeprecatedMethods = new IndirectlyDeprecatedMethods();
        indirectlyDeprecatedMethods.useSomethingDeprecated();
        IndirectlyDeprecatedMethodsForRemoval indirectlyDeprecatedMethodsForRemoval = new IndirectlyDeprecatedMethodsForRemoval();
        indirectlyDeprecatedMethodsForRemoval.useSomethingReallyDeprecated();
    }
}

In order to show the differences in behaviour, we copy this class and tag it as deprecated this time:

package jdk9.deprecated;

import org.deprecated.directly.DirectlyDeprecatedClass;
import org.deprecated.directly.DirectlyDeprecatedClassForRemoval;
import org.deprecated.directly.DirectlyDeprecatedMethods;
import org.deprecated.directly.DirectlyDeprecatedMethodsForRemoval;
import org.deprecated.indirectly.IndirectlyDeprecatedMethods;
import org.deprecated.indirectly.IndirectlyDeprecatedMethodsForRemoval;

/**
 * Shows the warnings concerning Deprecation shown in deprecated classes in JDK9
 * Created by Simon on 16.05.2017.
 */
@Deprecated(since = "9")
public class DeprecatedDeprecationTester {
    public static void main(String[] args) {
        DirectlyDeprecatedClass showsDeprecation = new DirectlyDeprecatedClass();
        DirectlyDeprecatedClassForRemoval showsDeprecationToo = new DirectlyDeprecatedClassForRemoval();
        DirectlyDeprecatedMethods directlyDeprecatedMethods = new DirectlyDeprecatedMethods();
        DirectlyDeprecatedMethodsForRemoval directlyDeprecatedMethodsForRemoval = new DirectlyDeprecatedMethodsForRemoval();
        directlyDeprecatedMethods.doDeprecatedThings();
        directlyDeprecatedMethodsForRemoval.doReallyDeprecatedThings();
        IndirectlyDeprecatedMethods indirectlyDeprecatedMethods = new IndirectlyDeprecatedMethods();
        indirectlyDeprecatedMethods.useSomethingDeprecated();
        IndirectlyDeprecatedMethodsForRemoval indirectlyDeprecatedMethodsForRemoval = new IndirectlyDeprecatedMethodsForRemoval();
        indirectlyDeprecatedMethodsForRemoval.useSomethingReallyDeprecated();
    }
}

As you can see, we differentiated in both those classes between deprecated classes, deprecated methods and direct/indirect deprecation. In this case indirect deprecation shows the call of a method or class, which is not deprecated itself, but uses deprecated code. This is an interesting case, as similar code could break your own calls when deprecated elements are finally removed, even without using the deprecated elements yourself. Finally we declare the used classes and methods in our other module:

package org.normal;

public class NothingDeprecated {
    public void doNothing(){
        //does nothing
    }
}
package org.deprecated.directly;

/**
 * Class annotated entirely as Deprecated, but not marked for removal
 * Created by Simon on 11.04.2017.
 */
@Deprecated(since = "9")
public class DirectlyDeprecatedClass {
    public void doDeprecatedThings() {
        //does deprecated Things
    }
}
package org.deprecated.directly;

/**
 * Class annotated entirely as Deprecated and marked for Removal
 * Created by Simon on 11.04.2017.
 */
@Deprecated(since = "9", forRemoval = true)
public class DirectlyDeprecatedClassForRemoval {
    public void doEntirelyDeprecatedThings(){
        //do something evil, which should be removed
    }
}
package org.deprecated.directly;

/**
 * Class with methods which are directly deprecated
 * Created by Simon on 11.04.2017.
 */
public class DirectlyDeprecatedMethods {
    @Deprecated(since = "9")
    public void doDeprecatedThings(){
        //does deprecated things
    }
}
package org.deprecated.directly;

/**
 * Class with deprecated methods, which are marked for removal
 * Created by Simon on 11.04.2017.
 */
public class DirectlyDeprecatedMethodsForRemoval {
    @Deprecated(since = "9", forRemoval = true)
    public void doReallyDeprecatedThings(){
        //does something evil to be removed
    }
}
package org.deprecated.indirectly;

import org.deprecated.directly.DirectlyDeprecatedMethods;

/**
 * Class with methods, which have deprecated dependencies
 * Created by Simon on 11.04.2017.
 */
public class IndirectlyDeprecatedMethods {
    public void useSomethingDeprecated(){
        DirectlyDeprecatedMethods directlyDeprecatedMethods = new DirectlyDeprecatedMethods();
        directlyDeprecatedMethods.doDeprecatedThings();
    }
}
package org.deprecated.indirectly;

import org.deprecated.directly.DirectlyDeprecatedMethodsForRemoval;

/**
 * Class with methods, which have deprecated dependencies marked for removal
 * Created by Simon on 11.04.2017.
 */
public class IndirectlyDeprecatedMethodsForRemoval {
    public void useSomethingReallyDeprecated(){
        DirectlyDeprecatedMethodsForRemoval directlyDeprecatedMethodsForRemoval = new DirectlyDeprecatedMethodsForRemoval();
        directlyDeprecatedMethodsForRemoval.doReallyDeprecatedThings();
    }
}

After this is said and done, we are set to try out the JEP 277 changes first hand. In order to do this we can first compile our two classes without any additional flags. Normally this wouldn’t show any warnings, but as we can see:

Even though we didn’t intend to get deprecation warnings, the compiler still shows three fresh warnings for the elements tagged for removal

The terminal warnings are shown without any additional configuration and independent to the state of the calling class itself. As mentioned in the warning we can get additional information by using -Xlint:deprecation as a flag, if we want to get the warnings for regular deprecation as well:

Please note, that even though we now get the regular warnings as well, they are still differentiated between [deprecation] and
[removal].

The neighbours

Something not to be forgotten is of course the javadoc tag @deprecated. Annotation and tag should always both be present.While the Annotation is used to tag the element itself for further code usage and tools, the tag provides the fineprint. This can be the reason of the deprecation itself, or alternatives to the usage of said deprecated element. Interestingly enough, the javac flag -Xlint:dep-ann considers a @deprecated tag without the corresponding annotation a mistake, while ignoring the reversed situation.

Another interesting fact is the difference between a terminal deprecation warning and a normal deprecation warning. We already saw above, that the terminal warning is a little bit harher in it’s description, but it also behaves differently. The all too popular @SuppressWarnings(„deprecation“) doesn’t supress terminal warnings! This is not to be underestimated, as it provides critical information for classes, which had their warnings supressed prior to the deprecation changes. Of course there is now a new @SuppressWarnings(„terminal“) to supress the terminal warnings. But the split between those two still requires you to go through your own legacy code and make the conscious decision of wether to refactor or to supress the new warning.

Summary

Well and that’s it for now. I still wanted to write a few words about the new tool jdeprscan, but i haven’t gotten around to try it yet, so this will probably follow as another article. So thanks for reading and till next time!

Hackathon, 18.05.2017

Heute hat endlich unser zweiter Hackathon stattgefunden. Dieses mal waren wir in einer Location außerhalb, nämlich hier. Der Raum war angenehm und für unsere Zwecke gut ausgestattet. Wir haben weiter an einer Spring-Boot-Anwendung gebastelt. Hier sind zwei Zeitraffervideos von der Veranstaltung.

Mir persönlich hat die Veranstaltung viel Spaß gebracht. Ich hoffe, im nächsten Quartal erneut einen Hackathon auf die Beine gestellt zu bekommen.

Kaffemaschine ins WLAN bringen

Wer wie ich Arduinos spannend findet und nach einem Projekt sucht, der kann ja darüber nachdenken seine Kaffeemaschine über WLAN zu steuern. Der Gedanke bei mir war, dass ich gerne morgens laufen gehe und wenn ich wieder zu Hause bin einen Kaffee trinken möchte. Oder aber auch morgens um eine bestimmte Uhrzeit von meiner Kaffeemaschine geweckt zu werden, weil dann der Kaffee schon fertig ist und man dadurch motivierter ist, aufzustehen.

Ich habe eine Tchibo-Caffisimo Compact. Diese hat 3 Knöpfe in einem Frontpanel, einen für Espresso, einen für Kaffee-Crema und einen für Filterkaffee. Ich hatte den Gedanken, die komplette Maschine auseinander zu schrauben, da ich gerne sowohl meine Anschlüsse als auch meinen Microcontroller in dem Gerät untergebracht bekommen hätte. Leider war ich nach 2 Stunden schrauben nicht in der Lage, das Gerät weiter auseinanderzunehmen. Ich hatte es geschafft den Unterboden zu öffnen, aber an die relevanten Teile war aus meiner Sicht kein rankommen.

Hier links im Bild sieht man jedoch die drei bereits erwähnten Schalter. Mein weiteres Vorgehen bestand dann darin, dass ich die „Gummikappe“ die die eigentlichen drei Schalter überdeckt, herausgezogen habe. Darunter waren dann drei einfache Schalter mit jeweils 4 Kontakten.

Da manche Kaffeemaschinen ihre Bedienelemente unter Netzspannung haben, habe ich nachgemessen, die Spannung lag bei 5V. Als nächstes habe ich testweise die Kontakte der Schalter der Reihe nach überbrückt. Dabei kam heraus, dass ich das Kochen von Kaffee durch das Überbrücken des unteren linken und oberen rechten Kontaktes auslösen kann. Daraufhin habe ich dünne Drähte genommen und an diese Kontakte angelötet. Um die Kontakte schalten zu können, habe ich mir folgendes Relais-Board gekauft: das Relaisboard Dazu habe ich mir folgenden Arduino-Klon mit integriertem WLAN gekauft: Arduino-Klon. Da es sich um einen nicht richtig lizensierten Klon handelt, muss man einen eigenen Treiber installieren, wenn ich alles richtig verstanden habe, dann deswegen, weil das Board tatsächlich keine Lizenz für USB hat. Link zu den Treibern beim Hersteller. Dummerweise hat der Treiber vom Hersteller es geschafft, meinen Mac zum Absturz zu bringen, etwas was mir tatsächlich vorher noch nie passiert war. Eine Lösung für dieses Problem habe ich hier gefunden. Anschließend habe ich einfach den Beispielcode genommen und minimal modifiziert:

#include <ESP8266WiFi.h>
#include <WiFiClient.h>
#include <ESP8266WebServer.h>
#include <ESP8266mDNS.h>

const char* ssid = "meine WLAN-SSID";
const char* password = "mein WLAN-Passwort";
const int relaisPin1 = 5;

ESP8266WebServer server(80);

const int led = 13;

void handleRoot() {
  digitalWrite(led, 1);
  server.send(200, "text/plain", "hello from esp8266!");
  digitalWrite(led, 0);
}

void handleNotFound(){
  digitalWrite(led, 1);
  String message = "File Not Found\n\n";
  message += "URI: ";
  message += server.uri();
  message += "\nMethod: ";
  message += (server.method() == HTTP_GET)?"GET":"POST";
  message += "\nArguments: ";
  message += server.args();
  message += "\n";
  for (uint8_t i=0; i<server.args(); i++){
    message += " " + server.argName(i) + ": " + server.arg(i) + "\n";
  }
  server.send(404, "text/plain", message);
  digitalWrite(led, 0);
}

void setup(void){
  pinMode(led, OUTPUT);
  pinMode(relaisPin1, OUTPUT);
  digitalWrite(led, 0);
  Serial.begin(115200);
  WiFi.begin(ssid, password);
  Serial.println("");

  // Wait for connection
  while (WiFi.status() != WL_CONNECTED) {
    delay(500);
    Serial.print(".");
  }
  Serial.println("");
  Serial.print("Connected to ");
  Serial.println(ssid);
  Serial.print("IP address: ");
  Serial.println(WiFi.localIP());

  if (MDNS.begin("esp8266")) {
    Serial.println("MDNS responder started");
  }

  server.on("/", handleRoot);

  server.on("/coffee", [](){
    digitalWrite(relaisPin1, HIGH);
    delay(1000);
    digitalWrite(relaisPin1, LOW);   
    server.send(200, "text/plain", "cooking coffee");
  });

  server.onNotFound(handleNotFound);

  server.begin();
  Serial.println("HTTP server started");
}

void loop(void){
  server.handleClient();
}

Das Ergebnis kann man sich in diesem Video ansehen:

Was ich effektiv mache ist in meinem Browser die IP des Microcontrollers aufzurufen. Wenn mein Code funktioniert, dann bekomme ich eine Seite in meinem Browser, die „hello from esp8266!“ ausgibt. Wenn ich an die URL ein „/coffee“ anhänge, kocht meine Maschine Kaffee. Nun sieht man aber in dem Video, dass jetzt „lose Drähte“ vorne aus meiner Kaffeemaschine raushängen und auch sonst sieht das eher unbenutzbar aus. Meine weitere Optimierung bestand darin, in die silberne Plastikabdeckung der Taster von seitlich rechts aus ein Loch zu bohren, durch das ich die Drähte führen konnte. Das ganze sah so ungefähr so aus wie hier rechts. Insgesamt ein lustiges Projekt, sehr befriedigend, allerdings nicht zu empfehlen, wenn man das Projekt nicht an einem Tag fertigstellen kann und keine zwei Kaffeemaschinen hat, da das bedeuten kann, dass man am zweiten Tag ohne Kaffee an dem Projekt arbeiten muss.