WidthUtils.java

  1. /*
  2.  * #%L
  3.  * wcm.io
  4.  * %%
  5.  * Copyright (C) 2020 wcm.io
  6.  * %%
  7.  * Licensed under the Apache License, Version 2.0 (the "License");
  8.  * you may not use this file except in compliance with the License.
  9.  * You may obtain a copy of the License at
  10.  *
  11.  *      http://www.apache.org/licenses/LICENSE-2.0
  12.  *
  13.  * Unless required by applicable law or agreed to in writing, software
  14.  * distributed under the License is distributed on an "AS IS" BASIS,
  15.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16.  * See the License for the specific language governing permissions and
  17.  * limitations under the License.
  18.  * #L%
  19.  */
  20. package io.wcm.handler.media.impl;

  21. import java.util.Arrays;
  22. import java.util.Objects;
  23. import java.util.regex.Matcher;
  24. import java.util.regex.Pattern;

  25. import org.apache.commons.lang3.StringUtils;
  26. import org.apache.commons.lang3.math.NumberUtils;
  27. import org.jetbrains.annotations.NotNull;
  28. import org.jetbrains.annotations.Nullable;

  29. import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
  30. import io.wcm.handler.media.MediaArgs.WidthOption;

  31. /**
  32.  * Helper methods for parsing strings with responsive widths (which can be optional).
  33.  */
  34. public final class WidthUtils {

  35.   // example values:
  36.   // 800        <- width=800px, mandatory
  37.   // 800?       <- width=800px, optional
  38.   // 800:1.5x   <- width=800px, density 1.5x, mandatory
  39.   // 800:1.5x?  <- width=800px, density 1.5x, optional
  40.   static final String WIDTH_OPTION = "\\d+(:\\d+(\\.\\d+)?x)?\\??";
  41.   static final Pattern WIDTH_OPTION_PATTERN = Pattern.compile("(?<width>\\d+)(:(?<density>\\d+(\\.\\d+)?x))?(?<optional>\\?)?");

  42.   // comma-separated width options; tolerates whitespaces between options.
  43.   // example values:
  44.   // 800,1024,2048
  45.   // 800,1024?,2048?   <- last two are optional
  46.   static final Pattern WIDTHS_PATTERN = Pattern.compile("^\\s*" + WIDTH_OPTION + "\\s*(,\\s*" + WIDTH_OPTION + "\\s*)*+$");

  47.   private WidthUtils() {
  48.     // static methods only
  49.   }

  50.   /**
  51.    * Parse widths string. The string should contain a comma-separated list of width options.
  52.    * Whitespaces between options are tolerated.<br>
  53.    * Examples:
  54.    * <ul>
  55.    *   <li>{@literal 100, 200? , 300?} returns three width options, the last two ones are optional</li>
  56.    *   <li>{@literal 100, 200:1.5x, 300:2x?} returns three options with pixel densities, last options is optional</li>
  57.    * </ul>
  58.    * @param widths Widths string
  59.    * @return Width options
  60.    */
  61.   @SuppressFBWarnings("NP_NONNULL_RETURN_VIOLATION")
  62.   public static @NotNull WidthOption @Nullable [] parseWidths(@Nullable String widths) {
  63.     if (StringUtils.isBlank(widths)) {
  64.       return null;
  65.     }
  66.     if (!WIDTHS_PATTERN.matcher(widths).matches()) {
  67.       return null;
  68.     }
  69.     String[] widthItems = StringUtils.split(widths, ",");
  70.     return Arrays.stream(widthItems)
  71.         .map(StringUtils::trim)
  72.         .map(WidthUtils::toWidthOption)
  73.         .filter(Objects::nonNull)
  74.         .toArray(WidthOption[]::new);
  75.   }

  76.   private static @Nullable WidthOption toWidthOption(@NotNull String widthOptionString) {
  77.     Matcher widthOptionMatcher = WIDTH_OPTION_PATTERN.matcher(widthOptionString);
  78.     if (!widthOptionMatcher.matches()) {
  79.       // this should never happen because we already checked against this pattern in the caller method,
  80.       // but we have to call matches() anyway
  81.       return null;
  82.     }

  83.     long width = NumberUtils.toLong(widthOptionMatcher.group("width"));
  84.     String density = widthOptionMatcher.group("density");
  85.     boolean mandatory = widthOptionMatcher.group("optional") == null;
  86.     return new WidthOption(width, density, mandatory);
  87.   }

  88.   /**
  89.    * @param widths Widths string
  90.    * @return true if the widths string is valid and contains density descriptor ":"
  91.    */
  92.   public static boolean hasDensityDescriptor(@Nullable String widths) {
  93.     // first make sure the widths string is valid
  94.     if (StringUtils.isBlank(widths) || !WIDTHS_PATTERN.matcher(widths).matches()) {
  95.       return false;
  96.     }
  97.     // now check if the valid string contains a density separator
  98.     return StringUtils.contains(widths, ":");
  99.   }

  100. }