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 (the
006 * "License"); you may not use this file except in compliance with the
007 * 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.http.header;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020
021import java.util.*;
022import java.util.function.*;
023
024import org.apache.juneau.commons.http.*;
025
026
027/**
028 * Base type for headers whose wire format is an <c>Accept</c>-style list of media ranges.
029 *
030 * <h5 class='section'>See Also:</h5><ul>
031 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/NextGenRestClient">Next-Generation REST Client</a>
032 * </ul>
033 *
034 * @since 9.2.1
035 */
036@SuppressWarnings({
037   "java:S2160", // equals() on HttpHeaderBean uses name + getValue(); typed state is reflected in getValue()
038   "unchecked" // Supplier<?> branches cast to typed suppliers after lazy-mode check
039})
040public class HttpMediaRangesHeader extends HttpHeaderBean {
041
042   /** Lazy supplier provides the wire string. */
043   public static final int LAZY_WIRE_STRING = 0;
044
045   /** Lazy supplier provides {@link MediaRanges} (re-evaluated on each access). */
046   public static final int LAZY_MEDIA_RANGES = 1;
047
048   private final MediaRanges cachedForStringOrDirect;
049   private final Supplier<?> lazySupplier;
050   private final int lazyMode;
051
052   /**
053    * @param name Header name. Must not be {@code null}.
054    * @param value Wire value. May be {@code null}.
055    */
056   protected HttpMediaRangesHeader(String name, String value) {
057      super(name, value);
058      this.cachedForStringOrDirect = value == null ? null : MediaRanges.of(value);
059      this.lazySupplier = null;
060      this.lazyMode = -1;
061   }
062
063   /**
064    * @param name Header name. Must not be {@code null}.
065    * @param value Parsed media ranges. May be {@code null}.
066    */
067   protected HttpMediaRangesHeader(String name, MediaRanges value) {
068      super(name, value == null ? null : value.toString());
069      this.cachedForStringOrDirect = value;
070      this.lazySupplier = null;
071      this.lazyMode = -1;
072   }
073
074   /**
075    * @param name Header name. Must not be {@code null}.
076    * @param supplier Lazy supplier; mode {@link #LAZY_WIRE_STRING} or {@link #LAZY_MEDIA_RANGES}. Must not be {@code null}.
077    * @param lazyMode Discriminator for erasure-safe lazy construction.
078    */
079   protected HttpMediaRangesHeader(String name, Supplier<?> supplier, int lazyMode) {
080      super(name, lazyMode == LAZY_WIRE_STRING
081         ? ((Supplier<String>) supplier)::get
082         : () -> {
083            var m = ((Supplier<MediaRanges>) supplier).get();
084            if (m == null)
085               return null;
086            return m.toString();
087         });
088      this.cachedForStringOrDirect = null;
089      this.lazySupplier = supplier;
090      this.lazyMode = lazyMode;
091   }
092
093   /**
094    * @return Parsed {@link MediaRanges}, or an empty {@link Optional} if the wire value is absent.
095    */
096   public Optional<MediaRanges> asMediaRanges() {
097      return o(toMediaRanges());
098   }
099
100   /**
101    * @param index Range index.
102    * @return The {@link MediaRange} at {@code index}, or {@code null} if out of range.
103    */
104   public MediaRange getRange(int index) {
105      var x = toMediaRanges();
106      return x == null ? null : x.getRange(index);
107   }
108
109   /**
110    * @param part Subtype fragment to find (e.g. {@code "activity"} for {@code text/json+activity}).
111    * @return {@code true} if any range's subtype contains the fragment.
112    */
113   public boolean hasSubtypePart(String part) {
114      var x = toMediaRanges();
115      return x != null && x.hasSubtypePart(part);
116   }
117
118   /**
119    * @param mediaTypes Candidate types, in preference order.
120    * @return Index of the best matching type, or {@code -1}.
121    */
122   public int match(List<? extends MediaType> mediaTypes) {
123      var x = toMediaRanges();
124      return x == null ? -1 : x.match(mediaTypes);
125   }
126
127   /**
128    * @param other Fallback when no parsed value is present.
129    * @return Parsed ranges or {@code other}.
130    */
131   public MediaRanges orElse(MediaRanges other) {
132      var x = toMediaRanges();
133      return nn(x) ? x : other;
134   }
135
136   /**
137    * @return Parsed {@link MediaRanges}, or {@code null} if the wire value is absent.
138    */
139   public MediaRanges toMediaRanges() {
140      if (lazyMode == LAZY_MEDIA_RANGES)
141         return ((Supplier<MediaRanges>) lazySupplier).get();
142      if (cachedForStringOrDirect != null)
143         return cachedForStringOrDirect;
144      var v = super.getValue();
145      return v == null ? null : MediaRanges.of(v);
146   }
147}