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.classic; 018 019import static org.apache.juneau.commons.utils.ObjectUtils.*; 020import static org.apache.juneau.commons.utils.Shorts.*; 021 022import java.util.*; 023 024import org.apache.http.*; 025import org.apache.http.impl.*; 026import org.apache.http.message.*; 027import org.apache.juneau.http.*; 028 029/** 030 * A basic implementation of the {@link StatusLine} interface. 031 * 032 * <p> 033 * Immutability is expressed with the "funnel + nested {@code Unmodifiable} snapshot" paradigm: every mutator routes 034 * through the single protected {@link #modify(Runnable)} choke-point, and {@link #unmodifiable()} returns a 035 * point-in-time snapshot of type {@link Unmodifiable} whose {@code modify(...)} override throws. 036 * 037 * <h5 class='section'>See Also:</h5><ul> 038 * <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a> 039 * </ul> 040 */ 041public class BasicStatusLine implements StatusLine { 042 043 /** 044 * Instantiates a new instance of this bean. 045 * 046 * @return A new bean. 047 */ 048 public static BasicStatusLine create() { 049 return new BasicStatusLine(); 050 } 051 052 /** 053 * Instantiates a new instance of this bean. 054 * 055 * @param statusCode The initial status code. 056 * @param reasonPhrase The initial reason phrase. Can be <jk>null</jk> to look up the reason phrase from the reason phrase catalog. 057 * @return A new bean. 058 */ 059 public static BasicStatusLine create(int statusCode, String reasonPhrase) { 060 return new BasicStatusLine().setStatusCode(statusCode).setReasonPhrase(reasonPhrase); 061 } 062 063 private static final ProtocolVersion DEFAULT_PROTOCOL_VERSION = new ProtocolVersion("HTTP", 1, 1); 064 065 private ProtocolVersion protocolVersion = DEFAULT_PROTOCOL_VERSION; 066 private int statusCode; 067 private String reasonPhrase; 068 private ReasonPhraseCatalog reasonPhraseCatalog; 069 private Locale locale = Locale.getDefault(); 070 071 /** 072 * Constructor. 073 */ 074 public BasicStatusLine() {} 075 076 /** 077 * Copy constructor. 078 * 079 * @param copyFrom The status line being copied. Must not be <jk>null</jk>. 080 */ 081 protected BasicStatusLine(BasicStatusLine copyFrom) { 082 this.protocolVersion = copyFrom.protocolVersion; 083 this.statusCode = copyFrom.statusCode; 084 this.reasonPhrase = copyFrom.reasonPhrase; 085 this.reasonPhraseCatalog = copyFrom.reasonPhraseCatalog; 086 this.locale = copyFrom.locale; 087 } 088 089 /** 090 * Returns a copy of this bean. 091 * 092 * @return A copy of this bean. 093 */ 094 public BasicStatusLine copy() { 095 return new BasicStatusLine(this); 096 } 097 098 /** 099 * Returns the locale of this status line. 100 * 101 * @return The locale of this status line. 102 */ 103 public Locale getLocale() { return locale; } 104 105 @Override /* Overridden from StatusLine */ 106 public ProtocolVersion getProtocolVersion() { return protocolVersion; } 107 108 @Override /* Overridden from StatusLine */ 109 public String getReasonPhrase() { 110 if (reasonPhrase == null) { 111 ReasonPhraseCatalog rfc = coalesce(reasonPhraseCatalog, EnglishReasonPhraseCatalog.INSTANCE); 112 return rfc.getReason(statusCode, locale); 113 } 114 return reasonPhrase; 115 } 116 117 @Override /* Overridden from StatusLine */ 118 public int getStatusCode() { return statusCode; } 119 120 /** 121 * Sets the locale used to retrieve reason phrases. 122 * 123 * <p> 124 * If not specified, uses {@link Locale#getDefault()}. 125 * 126 * @param value The new value. 127 * @return This object. 128 */ 129 public BasicStatusLine setLocale(Locale value) { 130 return modify(() -> locale = value); 131 } 132 133 /** 134 * Sets the protocol version on the status line. 135 * 136 * <p> 137 * If not specified, <js>"HTTP/1.1"</js> will be used. 138 * 139 * @param value The new value. 140 * @return This object. 141 */ 142 public BasicStatusLine setProtocolVersion(ProtocolVersion value) { 143 return modify(() -> protocolVersion = value); 144 } 145 146 /** 147 * Sets the reason phrase on the status line. 148 * 149 * <p> 150 * If not specified, the reason phrase will be retrieved from the reason phrase catalog 151 * using the locale on this bean. 152 * 153 * @param value The new value. Can be <jk>null</jk> to unset the property. 154 * @return This object. 155 */ 156 public BasicStatusLine setReasonPhrase(String value) { 157 return modify(() -> reasonPhrase = value); 158 } 159 160 /** 161 * Sets the reason phrase catalog used to retrieve reason phrases. 162 * 163 * <p> 164 * If not specified, uses {@link EnglishReasonPhraseCatalog}. 165 * 166 * @param value The new value. Can be <jk>null</jk> to unset the property. 167 * @return This object. 168 */ 169 public BasicStatusLine setReasonPhraseCatalog(ReasonPhraseCatalog value) { 170 return modify(() -> reasonPhraseCatalog = value); 171 } 172 173 /** 174 * Sets the status code on the status line. 175 * 176 * <p> 177 * If not specified, <c>0</c> will be used. 178 * 179 * @param value The new value. 180 * @return This object. 181 */ 182 public BasicStatusLine setStatusCode(int value) { 183 return modify(() -> statusCode = value); 184 } 185 186 /** 187 * Returns an unmodifiable snapshot of this bean. 188 * 189 * <p> 190 * The returned instance is a point-in-time copy of type {@link Unmodifiable}; any attempt to set a property on it 191 * throws an {@link UnsupportedOperationException}. This method is idempotent: if this bean is already unmodifiable 192 * it returns itself rather than taking another snapshot. The receiver is left unchanged (and still modifiable 193 * unless it was already an {@link Unmodifiable}). 194 * 195 * @return An unmodifiable snapshot of this bean, or this bean if it is already unmodifiable. 196 */ 197 public BasicStatusLine unmodifiable() { 198 return this instanceof UnmodifiableBean ? this : new Unmodifiable(this); 199 } 200 201 /** 202 * Returns whether this bean is unmodifiable. 203 * 204 * @return <jk>true</jk> if this bean is an {@link Unmodifiable} snapshot. 205 */ 206 public boolean isUnmodifiable() { 207 return this instanceof UnmodifiableBean; 208 } 209 210 @Override /* Overridden from Object */ 211 public String toString() { 212 return BasicLineFormatter.INSTANCE.formatStatusLine(null, this).toString(); 213 } 214 215 @Override 216 public boolean equals(Object o) { 217 return o instanceof BasicStatusLine other && eq(this, other, (x, y) -> 218 eq(x.protocolVersion, y.protocolVersion) && x.statusCode == y.statusCode 219 && eq(x.reasonPhrase, y.reasonPhrase) && eq(x.locale, y.locale)); 220 } 221 222 @Override 223 public int hashCode() { 224 return h(protocolVersion, statusCode, reasonPhrase, locale); 225 } 226 227 /** 228 * Single mutation funnel — the only choke-point through which all state changes on this bean flow. 229 * 230 * <p> 231 * Every mutator routes through this method. The {@link Unmodifiable} snapshot overrides only this method to 232 * throw, which freezes the entire mutation surface with a single override. 233 * 234 * @param mutation The state change to apply. 235 * @return This object. 236 */ 237 protected BasicStatusLine modify(Runnable mutation) { 238 mutation.run(); 239 return this; 240 } 241 242 /** 243 * Deep-freeze hook invoked from the {@link Unmodifiable} constructor after the snapshot copy completes. 244 * 245 * <p> 246 * This is a no-op for {@link BasicStatusLine}: all of its fields are immutable value types (protocol version, 247 * status code, reason phrase, locale, reason-phrase catalog) with no mutable sub-beans to deep-freeze. It exists 248 * as the paradigm's override point for families whose beans hold mutable sub-beans, which must be frozen here by 249 * <b>direct field assignment</b> (never via a setter, which would route through the throwing {@link #modify(Runnable)}). 250 */ 251 protected void freeze() { 252 // No mutable sub-beans to freeze. 253 } 254 255 /** 256 * An unmodifiable point-in-time snapshot of a {@link BasicStatusLine}. 257 * 258 * <p> 259 * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled 260 * through {@code modify(...)}, this single override freezes the entire mutation surface. 261 */ 262 public static class Unmodifiable extends BasicStatusLine implements UnmodifiableBean { 263 264 /** 265 * Constructor. 266 * 267 * @param copyFrom The status line to snapshot. Must not be <jk>null</jk>. 268 */ 269 @SuppressWarnings({ 270 "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans. 271 }) 272 protected Unmodifiable(BasicStatusLine copyFrom) { 273 super(copyFrom); 274 freeze(); 275 } 276 277 @Override /* Overridden from BasicStatusLine */ 278 protected BasicStatusLine modify(Runnable mutation) { 279 throw uoex("Bean is unmodifiable."); 280 } 281 } 282}