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.http;
018
019import static org.apache.juneau.commons.utils.Shorts.*;
020import static org.apache.juneau.commons.utils.StringUtils.*;
021
022import java.util.function.*;
023
024/**
025 * The HTTP protocol version that appears in request and response start lines (e.g. {@code "HTTP/1.1"}).
026 *
027 * <p>
028 * Mirrors the semantics of {@code org.apache.http.ProtocolVersion} without the Apache HttpCore dependency.
029 * The protocol name and major/minor numbers are stored separately so callers can compare versions numerically.
030 *
031 * <p>
032 * Create instances via {@link #of(String, int, int)} or {@link #parse(String)}:
033 * <p class='bjava'>
034 *    <jc>// "HTTP/1.1"</jc>
035 *    HttpProtocolVersion <jv>v1</jv> = HttpProtocolVersion.<jsf>HTTP_1_1</jsf>;
036 *
037 *    <jc>// Parse from a wire string</jc>
038 *    HttpProtocolVersion <jv>v2</jv> = HttpProtocolVersion.<jsm>parse</jsm>(<js>"HTTP/2.0"</js>);
039 * </p>
040 *
041 * @param protocol The protocol name (e.g. {@code "HTTP"}). Must not be <jk>null</jk>.
042 * @param major The major version number (e.g. {@code 1}).
043 * @param minor The minor version number (e.g. {@code 1}).
044 *
045 * @since 10.0.0
046 */
047public record HttpProtocolVersion(String protocol, int major, int minor) {
048
049   /** {@code HTTP/1.0}. */
050   public static final HttpProtocolVersion HTTP_1_0 = new HttpProtocolVersion("HTTP", 1, 0);
051
052   /** {@code HTTP/1.1}. */
053   public static final HttpProtocolVersion HTTP_1_1 = new HttpProtocolVersion("HTTP", 1, 1);
054
055   /** {@code HTTP/2.0}. */
056   public static final HttpProtocolVersion HTTP_2_0 = new HttpProtocolVersion("HTTP", 2, 0);
057
058   /**
059    * Canonical constructor.
060    *
061    * @param protocol The protocol name. Must not be <jk>null</jk>.
062    * @param major The major version (must be {@code >= 0}).
063    * @param minor The minor version (must be {@code >= 0}).
064    */
065   public HttpProtocolVersion {
066      reqnn("protocol", protocol);
067      if (major < 0)
068         throw iaex("Major version must be >= 0, got %s", major);
069      if (minor < 0)
070         throw iaex("Minor version must be >= 0, got %s", minor);
071   }
072
073   /**
074    * Creates a protocol version with the given protocol name and version numbers.
075    *
076    * @param protocol The protocol name (e.g. {@code "HTTP"}). Must not be <jk>null</jk>.
077    * @param major The major version number.
078    * @param minor The minor version number.
079    * @return A new instance. Never <jk>null</jk>.
080    */
081   public static HttpProtocolVersion of(String protocol, int major, int minor) {
082      return new HttpProtocolVersion(protocol, major, minor);
083   }
084
085   /**
086    * Parses a wire-format protocol version string.
087    *
088    * <p>
089    * Accepts the standard {@code PROTOCOL/major.minor} form (e.g. {@code "HTTP/1.1"}) and the abbreviated
090    * {@code PROTOCOL/major} form (e.g. {@code "HTTP/2"}, which is parsed as major=2, minor=0).
091    *
092    * @param s The protocol version string. Must not be <jk>null</jk>.
093    * @return A new instance. Never <jk>null</jk>.
094    * @throws IllegalArgumentException If the string cannot be parsed.
095    */
096   public static HttpProtocolVersion parse(String s) {
097      reqnn("s", s);
098      var slash = s.indexOf('/');
099      if (slash < 0)
100         throw iaex("Invalid protocol version: %s", s);
101      var protocol = s.substring(0, slash);
102      var rest = s.substring(slash + 1);
103      var dot = rest.indexOf('.');
104      Supplier<RuntimeException> err = () -> iaex("Invalid protocol version: %s", s);
105      if (dot < 0)
106         return new HttpProtocolVersion(protocol, parseInt(rest, err), 0);
107      return new HttpProtocolVersion(protocol, parseInt(rest.substring(0, dot), err), parseInt(rest.substring(dot + 1), err));
108   }
109
110   /**
111    * Returns the canonical wire-format string (e.g. {@code "HTTP/1.1"}).
112    *
113    * @return The wire-format string. Never <jk>null</jk>.
114    */
115   @Override /* Object */
116   public String toString() {
117      return protocol + "/" + major + "." + minor;
118   }
119}