recursively call a method but log only second time?

149 views Asked by At

I have a below method which does URL decoding on the value passed along with using charset.

  public String decodeValue(String value, Charset charset) {
    if (!Strings.isNullOrEmpty(value)) {
      try {
        value = URLDecoder.decode(value, charset.name());
      } catch (UnsupportedEncodingException ex) {
        // log error
        return null;
      }
    }
    return value;
  }

Now if URLDecoder.decode line throws UnsupportedEncodingException first time then I want to run same value against below three lines:

value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
value = value.replaceAll("\\+", "%2B");
value = URLDecoder.decode(value, charset.name());

And if then again URLDecoder.decode line throws exception second time, then I will log the error but only second time and return null value otherwise return the value which is decoded.

What is the best and elegant way to do this?

5

There are 5 answers

3
merlin2011 On BEST ANSWER

The easiest way is to make a private version of your function signature which includes an extra flag.

  private String decodeValue(String value, Charset charset, boolean isFirstTime) {
    if (!Strings.isNullOrEmpty(value)) {
      try {
        value = URLDecoder.decode(value, charset.name());
      } catch (UnsupportedEncodingException ex) {
        if (isFirstTime) {
            value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
            value = value.replaceAll("\\+", "%2B");
            return decodeValue(value, charset.name(), false);
        } else {
            // log error
            return null;
        }
      }
    }
    return value;
  }

Then, just pass true the first time and false in the recursive call. Inside the function, only execute the next three lines if true is passed.

The public version can pass true.

  public String decodeValue(String value, Charset charset) {
       decodeValue(value, charset, true);
  }
1
Siddharth Kumar On

Here you go :

 public String decode(String value, Charset charset) {
    if (!Strings.isNullOrEmpty(value)) {
      try {
        value = URLDecoder.decode(value, charset.name());
      } catch (UnsupportedEncodingException ex) {
        try {
        value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
        value = value.replaceAll("\\+", "%2B");
        value = URLDecoder.decode(value, charset.name());
      } catch (UnsupportedEncodingException ex) {
        // log error
        return null;
      }
      }
    }
    return value;
  }

Hope this solves your problem.

1
GhostCat On

Adding a flag is one option. Yes, it is the easier one; but many people argue that having flags is simply spoken: bad practice.

You simply try to minimize those things.

In other words: if you have a method that should behave differently for the first and subsequent calls; consider creating two methods there. Of course, you avoid code duplication as far as possible, but unless it becomes to costly, you should at least consider avoiding such flag arguments, too.

0
Matthew Wright On

While it isn't recursive, you could make use of a while loop and a flag.

public String decode(String value, Charset charset) {
    boolean first = true;
    while(!Strings.isNullOrEmpty(value)) {
        try {
            return value = URLDecoder.decode(value, charset);
        } catch (UnsupportedEncodingException e) {
            if(first == false) {
                // Log error.
                return null;
            }
            value = value.replaceAll("%(?![0-9a-fA-F]{2})", "%25").replaceAll("\\+", "%2B");
        }
        first = false;
    }
    return value;
}
2
Dmitry Gorkovets On

Here is version without extra flags, duplicated code, recursion and loops:

public String decodeValue(final String value, Charset charset) throws UnsupportedEncodingException {
    String result;
    if (!Strings.isNullOrEmpty(value)) {
        UnsupportedEncodingException[] lastException = new UnsupportedEncodingException[1];
        result = Stream.<Function<String, String>>of(
                Function.identity(),
                s -> {
                    s = s.replaceAll("%(?![0-9a-fA-F]{2})", "%25");
                    s = s.replaceAll("\\+", "%2B");
                    return s;
                })
                .map(f -> f.apply(value))
                .map(appliedValue -> {
                    try {
                        return URLDecoder.decode(appliedValue, charset.name());
                    } catch (UnsupportedEncodingException e) {
                        lastException[0] = e;
                        return null;
                    }
                })
                .filter(Objects::nonNull)
                .findFirst()
                .orElseThrow(() -> lastException[0]);
    }
    return result;
}

Here I just take stream with 2 functions: identity and function making string correction. Then I apply these functions to initial string and try to decode. If first attempt is successful then code will not apply correction function and just return correct result. If decoder throws exception after value correction then "findFirst" will not find any value. Then we throw last caught exception.