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}