Mittwoch, 20. Juli 2011

Merkzettel: @NotNull Check mit aspectj

Möchte man nicht mit assert arbeiten um Parameter auf null zu prüfen, bietet sich aspectj an um einen komfortablen NotNull-Check zu implementieren. Vor allem wenn aspectj schon im Projekt vorhanden ist.

Anforderung: Alle Parameter in Methoden und Konstruktoren, die die Annotation @NotNull besitzen sollen geprüft werden. Wenn ein so markierter Parameter null ist, soll eine IllegalArgumentException ausgelöst werden.

Hier die Beispielimplementierung:

Als erstes die Annotation @NotNull für die zu prüfenden Parameter:

@Target({ ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
public @interface NotNull {

  String parameterName() default "";
}

Die Annotation @NotNull besitzt das Attribut parameterName. Hier kann der Name des Parameters übergeben werden. Dieser wird für die Fehlermeldung verwendet.



Dann benötigen wir einen Aspect, der alle Methoden und Konstruktoren mit der Annotation @NotNull "trifft".

Pointcut für alle Methoden mit der Annotation @NotNull: execution(* *(..,@NotNull (*),..))

Pointcut für alle Konstruktoren mit der Annotation @NotNull: execution(new(..,@NotNull (*),..))

@Aspect
public class CheckArgumentsAspect {

  /**
   * Der Pointcut trifft auf alle Methoden in allen Packages zu, die
   * mindestens einen Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(* *(..,@NotNull (*),..))")
  public Object checkArgsForMethod(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof MethodSignature) {

      MethodSignature methodSignature = (MethodSignature) joinPoint
          .getSignature();
      Method method = methodSignature.getMethod();
      Class<?>[] parameterTypes = method.getParameterTypes();
      Annotation[][] parameterAnnotationArray = method
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter der Methode '"
          + methodSignature.toLongString() + "' mit den Parameterwerten '"
          + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, methodSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Der Pointcut trifft auf alle Konstruktoren zu, die mindestens einen
   * Parameter mit der Annotation {@link NotNull} besitzen.
   */
  @Around("execution(new(..,@NotNull (*),..))")
  public Object checkArgsForConstructor(ProceedingJoinPoint joinPoint)
      throws Throwable {

    if (joinPoint.getSignature() instanceof ConstructorSignature) {

      ConstructorSignature constructorSignature = 
          (ConstructorSignature) joinPoint.getSignature();
      Constructor<?> constructor = constructorSignature.getConstructor();
      Class<?>[] parameterTypes = constructor.getParameterTypes();
      Annotation[][] parameterAnnotationArray = constructor
          .getParameterAnnotations();

      System.out.println("Prüfe Parameter des Konstruktors '"
          + constructorSignature.toLongString()
          + "' mit den Parameterwerten '" + joinPoint.getArgs() + "'.");

      // Prüft alle Parameter
      checkParameter(joinPoint.getArgs(), parameterTypes,
          parameterAnnotationArray, constructorSignature.toLongString());
    }

    return joinPoint.proceed();
  }

  /**
   * Prüft alle Parameter.
   * 
   * @param parameter
   *          Alle Parameter.
   * @param parameterTypes
   *          Die Typen zu den Parametern.
   * @param parameterAnnotationArray
   *          Die Annotations zu den Parametern.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkParameter(Object[] parameter, Class<?>[] parameterTypes,
      Annotation[][] parameterAnnotationArray, String signature) {

    for (int i = 0; i < parameterTypes.length; i++) {

      Annotation[] parameterAnnotations = parameterAnnotationArray[i];

      for (Annotation annotation : parameterAnnotations) {

        if (annotation instanceof NotNull) {

          // Hier wird das Argument auf null geprüft
          checkNotNull(parameter[i], parameterTypes[i], i,
              ((NotNull) annotation).parameterName(), signature);
        }
      }
    }
  }

  /**
   * Prüft den Parameter auf null.
   * 
   * @param parameter
   *          Der Parameter.
   * @param parameterType
   *          Der Typ des Parameters.
   * @param parameterIndex
   *          Der Index des Parameters.
   * @param parameterName
   *          Der Name des Parameters.
   * @param signature
   *          Die Signatur der Methode oder des Konstruktors.
   */
  private void checkNotNull(Object parameter, Class<?> parameterType,
      int parameterIndex, String parameterName, String signature) {

    // Hier wird das Argument auf null geprüft
    if (parameter == null) {

      if (StringUtils.isBlank(parameterName)) {
        parameterName = "-";
      }

      String longMsg = MessageFormat.format(
          "Fehler: Der {0}. Parameter (Name: {1}, Typ: {2}) von {3} "
              + "ist null.", parameterIndex + 1, parameterName,
          parameterType.getName(), signature);
      throw new IllegalArgumentException(longMsg);
    }
  }
}


Eine Klasse mit zu prüfenden Parametern könnte zum Beispiel so aussehen:

public class TestClass {

  public TestClass() {
    super();
  }

  public TestClass(@NotNull(parameterName = "kNotNull") Integer kNotNull) {
    super();
    System.out.println("    kNotNull: " + kNotNull);
  }

  public void methodWithOneNotNullArg(
      @NotNull(parameterName = "aNotNull") Integer aNotNull) {
    System.out.println("    aNotNull: " + aNotNull);
  }

  public void methodWithOneNotNullArg(Long b,
      @NotNull(parameterName = "cNotNull") Integer cNotNull) {
    System.out.println("    b       : " + b);
    System.out.println("    cNotNull: " + cNotNull);
  }

  public void methodWithTwoNotNullArg(
      @NotNull(parameterName = "dNotNull") Integer dNotNull,
      @NotNull Integer eNotNull) {
    System.out.println("    dNotNull: " + dNotNull);
    System.out.println("    eNotNull: " + eNotNull);
  }

  public void methodWithoutNotNullArg(Long f) {
    System.out.println("    f       : " + f);
  }
}


Jetzt sollten alle Parameter mit der Annotation @NotNull geprüft werden. Hier noch der passende JUnit-Test dazu:

public class TestClassTest {

  private TestClass testClass = new TestClass();

  @Test
  public void testMethodWithOneNotNullArgInteger() {
    testClass.methodWithOneNotNullArg(1);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerNull() {
    try {
      testClass.methodWithOneNotNullArg(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerInteger() {
    testClass.methodWithOneNotNullArg(11l, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull1() {
    testClass.methodWithOneNotNullArg(null, 11);
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNull2() {
    try {
      testClass.methodWithOneNotNullArg(11l, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithOneNotNullArgIntegerIntegerNullNull() {
    try {
      testClass.methodWithOneNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArg() {
    testClass.methodWithTwoNotNullArg(111, 111);
  }

  @Test
  public void testMethodWithTwoNotNullArgNull1() {
    try {
      testClass.methodWithTwoNotNullArg(null, 222);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNull2() {
    try {
      testClass.methodWithTwoNotNullArg(222, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithTwoNotNullArgNullNull() {
    try {
      testClass.methodWithTwoNotNullArg(null, null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }

  @Test
  public void testMethodWithoutNotNullArg() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testMethodWithoutNotNullArgNull() {
    testClass.methodWithoutNotNullArg(null);
  }

  @Test
  public void testConstructorWithNotNullArg() {
    new TestClass(999);
  }

  @Test
  public void testConstructorWithNotNullArgNull() {
    try {
      new TestClass(null);
      Assert.fail("Hier wurde eine IllegalArgumentException erwartet.");
    } catch (IllegalArgumentException e) {
      Assert.assertNotNull(e);
      System.err.println("    " + e.getMessage());
    }
  }
}

Und jetzt oft @NotNull verwenden und weitere Annotations zum Prüfen von Argumenten erstellen!

Das Projekt befindet sich hier.

Mittwoch, 6. Juli 2011

Merkzettel: Java Prozesse beenden

Auch Java-Programme müssen manchmal mit Systemmitteln beendet werden. Dafür benötigt man die Prozess-ID PID. Diese kann beispielsweise auf einem UNIX oder LINUX-System mit dem Befehl ps angezeigt werden. Aber der Befehl führt oft nicht zum gewünschten Ergebnis:
> ps -A |grep java
  2016 ?           2:41 java
  5875 pts/2       0:05 java
 29278 ?           2:42 java
 26519 ?           0:23 java
 26623 ?           0:28 java
 26480 ?           0:24 java
 26146 ?           2:36 java
 26568 ?           0:24 java
 26668 ?           0:34 java
 26667 ?           1:10 java
  5588 pts/2       4:07 java
 26145 ?           0:10 java
  
Um herauszufinden, welcher Java-Prozess beendet werden soll bietet sich der Befehl <JAVA_HOME>/bin/jps an:
> /opt/java/jdk1.5/bin/jps
5996 Jps
26667 jar
5588 start.jar
5875 Main
29278 start.jar
5995 Main
  
Mehr Informationen erhält man mit den Optionen -v für die Argumente und -l für den Package Namen der Main-Klasse.
> /opt/java/jdk1.5/bin/jps -lv
6160 sun.tools.jps.Jps -Dapplication.home=/opt/regr/java/jdk1.5 -Xms8m
26667 XyzServer.jar
29278 /opt/jetty7.1.6/start.jar -Xmx1024M -Xms512M -Djetty.port=8080 -DSTOP.PORT=8079
  
Jetzt weiß ich das zu meinen Jetty-Server (mit Port 8080) die PID 29278 gehört!
> kill -9 29278
Und weg ist er!