001/*
002 * Licensed to the Apache Software Foundation (ASF) under one or more
003 * contributor license agreements.  See the NOTICE file distributed with
004 * this work for additional information regarding copyright ownership.
005 * The ASF licenses this file to You under the Apache License, Version 2.0
006 * (the "License"); you may not use this file except in compliance with
007 * the License.  You may obtain a copy of the License at
008 *
009 *      http://www.apache.org/licenses/LICENSE-2.0
010 *
011 * Unless required by applicable law or agreed to in writing, software
012 * distributed under the License is distributed on an "AS IS" BASIS,
013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 * See the License for the specific language governing permissions and
015 * limitations under the License.
016 */
017package org.apache.juneau.console.freemarker;
018
019import java.util.*;
020import java.util.regex.*;
021
022/**
023 * A rail section of quick time-range presets, with an optional custom from/to.
024 *
025 * <p>
026 * Publishes {@code {schemaVersion:1, viewId, options:{range}, start, end}} on its {@code filter:} topic, where
027 * {@code range} is the preset or {@code "custom"} and {@code start}/{@code end} are ISO-8601 UTC instants.
028 * {@code Nd} and {@code Nh} end at now floored to the minute; {@code ytd} starts at January 1, 00:00 UTC.
029 * </p>
030 *
031 * <h5 class='section'>Example:</h5>
032 * <p class='bjava'>
033 *    RangeSection.<jsm>quick</jsm>(<js>"7d"</js>, <js>"30d"</js>, <js>"90d"</js>, <js>"ytd"</js>).custom(<jk>true</jk>).defaultPreset(<js>"30d"</js>)
034 *       .publishes(Topics.<jsm>filter</jsm>(<js>"range"</js>));
035 * </p>
036 *
037 * @since 10.0.0
038 */
039@SuppressWarnings({
040   "java:S1192" // Repeated literals are contract wire keys; named constants would obscure the contract shape.
041})
042public final class RangeSection extends Section {
043
044   private static final Pattern PRESET = Pattern.compile("^([1-9]\\d{0,3}[dh]|ytd)$");
045
046   private final List<String> presets;
047   private boolean custom;
048   private String defaultPreset;
049
050   private RangeSection(String...presets) {
051      this.presets = Arrays.asList(presets);  // A null preset is reported by check(), not an NPE here.
052   }
053
054   /**
055    * Creates a range section.
056    *
057    * @param presets The quick presets, each {@code Nd}, {@code Nh} or {@code ytd}.
058    * @return A new section.
059    */
060   public static RangeSection quick(String...presets) {
061      return new RangeSection(presets);
062   }
063
064   /**
065    * Adds the custom from/to inputs (read as UTC).
066    *
067    * @param value <jk>true</jk> to show them.
068    * @return This object.
069    */
070   public RangeSection custom(boolean value) {
071      custom = value;
072      return this;
073   }
074
075   /**
076    * Sets the preset used when nothing is restored.
077    *
078    * @param value One of the presets.  Defaults to the first.
079    * @return This object.
080    */
081   public RangeSection defaultPreset(String value) {
082      defaultPreset = value;
083      return this;
084   }
085
086   @Override /* Section */
087   public RangeSection publishes(String topic) {
088      super.publishes(topic);
089      return this;
090   }
091
092   @Override /* Section */
093   public RangeSection label(String label) {
094      super.label(label);
095      return this;
096   }
097
098   @Override String kind() { return "range"; }
099
100   @Override void check(String dash) {
101      super.check(dash);
102      if (presets.isEmpty())
103         throw e77(dash, "range section '" + id() + "' needs at least one preset");
104      var seen = new HashSet<String>();
105      for (var p : presets) {
106         if (p == null || ! PRESET.matcher(p).matches())
107            throw e77(dash, "range section '" + id() + "' preset '" + p + "' is not Nd, Nh or ytd");
108         if (! seen.add(p))
109            throw e77(dash, "range section '" + id() + "' lists preset '" + p + "' twice");
110      }
111      if (defaultPreset != null && ! presets.contains(defaultPreset))
112         throw e77(dash, "range section '" + id() + "' default preset '" + defaultPreset + "' is not one of its presets");
113   }
114
115   @Override void body(Map<String,Object> m) {
116      m.put("presets", presets);
117      m.put("custom", custom);
118      m.put("defaultPreset", defaultPreset != null ? defaultPreset : presets.get(0));
119   }
120}