Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.java.programmer > #36725

Re: How to handle same sanity checks

From Arne Vajhøj <arne@vajhoej.dk>
Newsgroups comp.lang.java.programmer
Subject Re: How to handle same sanity checks
Date 2017-06-25 22:55 -0400
Organization Aioe.org NNTP Server
Message-ID <oipt3o$12ek$1@gioia.aioe.org> (permalink)
References <87o9tgo0a9.fsf@munus.decebal.nl> <oiga3s$jrd$1@dont-email.me> <oih2hb$bad$1@Gaia.teknon.de> <87fuernwav.fsf@munus.decebal.nl>

Show all headers | View raw


On 6/23/2017 12:48 AM, Cecil Westerhof wrote:
> On Thursday 22 Jun 2017 20:33 CEST, Volker Borchert wrote:
>> Eric Sosman wrote:
>>> On 6/22/2017 5:10 AM, Cecil Westerhof wrote:
>>>> In my code I have several methods that start with:
>>>> if ((n < MIN_VAL) || (n > MAX_VAL)) {
>>>> throw new InvalidParameterException(String.format(ERROR_N, n));
>>>> }
>>>>
>>>> That is not really DRY.
>>>>
>>>> I could write a method like:
>>>> private void checkN(n) {
>>>> if ((n < MIN_VAL) || (n > MAX_VAL)) {
>>>> throw new InvalidParameterException(String.format(ERROR_N, n));
>>>> }
>>>> }
>>>>
>>>> And then replace all the checks with a call to checkN. But then
>>>> the exception is generated in the method checkN instead of the
>>>> method that has a wrong parameter. What is the best way to handle
>>>> this?
>>>
>>> Tastes surely vary, but for repetitive checks I favor writing the
>>> private internal helper method. Yes, the caller of doItPlease()
>>> gets an exception originating from internalSanityCheck() instead,
>>> but that shouldn't bother anyone. doItPlease() will be in the stack
>>> trace anyhow, even though it's not at the very top.
>>
>> That can be a problem with some more-or-less-broken logging
>> frameworks, and a nuisance if trying to diagnose over the phone.
>>
>> You can always do something like
>>
>> 	final InvalidParameterException e
>> 	  = new InvalidParameterException(String.format(ERROR_N, n));
>> 	final StackTraceElement[] s = e.getStackTrace();
>> 	e.setStackTrace(Arrays.copyOfRange(s, 1, s.length));
>> 	throw e;
> 
> That is a very good idea. I have now:
>      private static void checkInRange(final int n,
>                                       final int lowLimit,
>                                       final int highLimit,
>                                       final String message) {
>          if ((n < lowLimit) || (n > highLimit)) {
>              final InvalidParameterException e =
>                  new InvalidParameterException(
>                      String.format(message, lowLimit, highLimit, n));
>              final StackTraceElement[] s = e.getStackTrace();
>              e.setStackTrace(Arrays.copyOfRange(s, 1, s.length));
>              throw e;
>          }
>      }
> 
> Works like a charm.

> The code is now more clear and easier to change, while the stack trace
> gives the right information.

That is probably as far as you can get in pure Java.

If you are willing to use AspectJ with annotations there is
another way.

Demo:

package paramcheck;

import java.util.function.Consumer;

public class TestMain {
	@CheckParams
	public static void test1(@IntIntervalCheck(minValue=1,maxValue=10) int v) {
		System.out.println(v + " is OK");
	}
	@CheckParams
	public static void test2(@StringLengthCheck(minLength=1,maxLength=10) 
@StringPatternCheck(pattern="[A-Z]\\d+") String s) {
		System.out.println(s + " is OK");
	}
	public static <T> void test(Consumer<T> f, T o) {
		try {
			f.accept(o);
		} catch(ParamException ex) {
			System.out.println(ex.getMessage());
			ex.printStackTrace();
		}
	}
	public static void main(String[] args) {
		test((o) -> test1(o), 5);
		test((o) -> test1(o), 50);
		test((o) -> test2(o), "A123");
		test((o) -> test2(o), "XXXXXXXXXXXXXXX");
		test((o) -> test2(o), "ABCD");
	}
}

Output:

5 is OK
50 not in interval
paramcheck.ParamException: 50 not in interval
         at paramcheck.TestMain.test1(TestMain.java:8)
         at paramcheck.TestMain.lambda$1(TestMain.java:24)
         at paramcheck.TestMain.test(TestMain.java:16)
         at paramcheck.TestMain.main(TestMain.java:24)
A123 is OK
XXXXXXXXXXXXXXX invalid length
paramcheck.ParamException: XXXXXXXXXXXXXXX invalid length
         at paramcheck.TestMain.test2(TestMain.java:12)
         at paramcheck.TestMain.lambda$3(TestMain.java:26)
         at paramcheck.TestMain.test(TestMain.java:16)
         at paramcheck.TestMain.main(TestMain.java:26)
ABCD does not match pattern
paramcheck.ParamException: ABCD does not match pattern
         at paramcheck.TestMain.test2(TestMain.java:12)
         at paramcheck.TestMain.lambda$4(TestMain.java:27)
         at paramcheck.TestMain.test(TestMain.java:16)
         at paramcheck.TestMain.main(TestMain.java:27)

(see the rest of the code below)

I think it is nice code. But it also has its drawbacks - it
brings in an entire new paradigm.

Arne

package paramcheck;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface IntIntervalCheck {
	public int minValue() default 0;
	public int maxValue() default Integer.MAX_VALUE;
	public String message() default "%d not in interval";
}

package paramcheck;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface StringLengthCheck {
	public int minLength() default 0;
	public int maxLength() default 255;
	public String message() default "%s invalid length";
}

package paramcheck;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface StringPatternCheck {
	public String pattern() default ".*";
	public String message() default "%s does not match pattern";
}

package paramcheck;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface CheckParams {
}

package paramcheck;

import java.lang.reflect.Method;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;

@Aspect
public class CheckParamsAdvice {
	@Pointcut("execution(@paramcheck.CheckParams * *.*(..))")
	public void checkParamsPointcut() { }
	@Before("checkParamsPointcut()")
	public void checkParams(JoinPoint jp) {
		try {
			Method m = ((MethodSignature)jp.getSignature()).getMethod();
			Object[] args = jp.getArgs();
			for(int i = 0; i < args.length; i++) {
				ParamsChecker.check(m.getParameterAnnotations()[i], args[i]);	
			}
		} catch (SecurityException e) {
			e.printStackTrace();
		}
	}
}

package paramcheck;

import java.lang.annotation.Annotation;
import java.util.Arrays;
import java.util.regex.Pattern;

public class ParamsChecker {
	public static void throwException(String msg) {
		ParamException ex = new ParamException(msg);
		StackTraceElement[] ste = ex.getStackTrace();
		ex.setStackTrace(Arrays.copyOfRange(ste, 3, ste.length));
		throw ex;
	}
	public static void check(Annotation[] ans, Object o) {
		for(Annotation an : ans) {
			if(an instanceof IntIntervalCheck) {
				IntIntervalCheck iic = (IntIntervalCheck)an;
				int v = (int)o;
				if(v < iic.minValue() || iic.maxValue() < v) {
					throwException(String.format(iic.message(), v));
				}
			}
			if(an instanceof StringLengthCheck) {
				StringLengthCheck slc = (StringLengthCheck)an;
				String s = (String)o;
				if(s.length() < slc.minLength() || slc.maxLength() < s.length()) {
					throwException(String.format(slc.message(), s));
				}
			}
			if(an instanceof StringPatternCheck) {
				StringPatternCheck spc = (StringPatternCheck)an;
				String s = (String)o;
				if(!Pattern.matches(spc.pattern(), s)) {
					throwException(String.format(spc.message(), s));
				}
			}
		}
	}
}

package paramcheck;

public class ParamException extends RuntimeException {
	private static final long serialVersionUID = 1L;
	public ParamException(String msg) {
		super(msg);
	}
}





Back to comp.lang.java.programmer | Previous | Next — Previous in thread | Next in thread | Find similar | Unroll thread


Thread

How to handle same sanity checks Cecil Westerhof <Cecil@decebal.nl> - 2017-06-22 11:10 +0200
  Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-22 10:27 +0000
    Re: How to handle same sanity checks Cecil Westerhof <Cecil@decebal.nl> - 2017-06-22 13:21 +0200
    Re: How to handle same sanity checks Cecil Westerhof <Cecil@decebal.nl> - 2017-06-23 06:51 +0200
    Re: How to handle same sanity checks Robert Klemme <shortcutter@googlemail.com> - 2017-06-24 12:06 +0200
      Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-24 10:33 +0000
        Re: How to handle same sanity checks Robert Klemme <shortcutter@googlemail.com> - 2017-06-24 16:46 +0200
          Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-24 19:37 +0000
  Re: How to handle same sanity checks Andreas Leitgeb <avl@logic.at> - 2017-06-22 11:26 +0000
    Re: How to handle same sanity checks Eric Sosman <esosman@comcast-dot-net.invalid> - 2017-06-22 07:44 -0400
      Re: How to handle same sanity checks Andreas Leitgeb <avl@logic.at> - 2017-06-22 12:30 +0000
  Re: How to handle same sanity checks Eric Sosman <esosman@comcast-dot-net.invalid> - 2017-06-22 07:40 -0400
    Re: How to handle same sanity checks v_borchert@despammed.com (Volker Borchert) - 2017-06-22 18:33 +0000
      Re: How to handle same sanity checks Cecil Westerhof <Cecil@decebal.nl> - 2017-06-23 06:48 +0200
        Re: How to handle same sanity checks Eric Sosman <esosman@comcast-dot-net.invalid> - 2017-06-23 09:57 -0400
          Re: How to handle same sanity checks Cecil Westerhof <Cecil@decebal.nl> - 2017-06-23 16:31 +0200
            Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-23 17:21 +0000
              Re: How to handle same sanity checks Eric Douglas <e.d.programmer@gmail.com> - 2017-06-26 05:21 -0700
                Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-26 13:34 +0000
                Re: How to handle same sanity checks Martin Gregorie <martin@address-in-sig.invalid> - 2017-06-26 15:22 +0000
        Re: How to handle same sanity checks Arne Vajhøj <arne@vajhoej.dk> - 2017-06-25 22:55 -0400
  Re: How to handle same sanity checks Marcel Mueller <news.5.maazl@spamgourmet.org> - 2017-06-22 23:07 +0200
  Re: How to handle same sanity checks Joerg Meier <joergmmeier@arcor.de> - 2017-06-25 09:52 +0200
  Re: How to handle same sanity checks Robert Klemme <shortcutter@googlemail.com> - 2017-06-25 12:31 +0200
    Re: How to handle same sanity checks Arne Vajhøj <arne@vajhoej.dk> - 2017-06-25 14:23 -0400
      Style police (Was: Re: How to handle same sanity checks) Eric Sosman <esosman@comcast-dot-net.invalid> - 2017-06-25 17:28 -0400
        Re: Style police (Was: Re: How to handle same sanity checks) Arne Vajhøj <arne@vajhoej.dk> - 2017-06-25 19:14 -0400
        Re: Style police (Was: Re: How to handle same sanity checks) Arne Vajhøj <arne@vajhoej.dk> - 2017-06-25 19:19 -0400
          Re: Style police (Was: Re: How to handle same sanity checks) Robert Klemme <shortcutter@googlemail.com> - 2017-06-26 23:34 +0200

csiph-web