View Javadoc
1   /*
2    * SPDX-FileCopyrightText: Copyright (c) 2011-2025 Yegor Bugayenko
3    * SPDX-License-Identifier: MIT
4    */
5   package com.jcabi.http.mock;
6   
7   import com.jcabi.aspects.Immutable;
8   import com.jcabi.aspects.Loggable;
9   import com.jcabi.http.ImmutableHeader;
10  import com.jcabi.http.RequestBody;
11  import com.jcabi.immutable.Array;
12  import com.jcabi.log.Logger;
13  import java.net.HttpURLConnection;
14  import java.nio.charset.Charset;
15  import java.nio.charset.StandardCharsets;
16  import java.util.LinkedList;
17  import java.util.List;
18  import java.util.Map;
19  import java.util.concurrent.ConcurrentHashMap;
20  import java.util.concurrent.ConcurrentMap;
21  import lombok.EqualsAndHashCode;
22  
23  /**
24   * Mock response.
25   *
26   * @since 0.10
27   */
28  @Immutable
29  @SuppressWarnings("PMD.TooManyMethods")
30  public interface MkAnswer {
31  
32      /**
33       * HTTP response status.
34       * @return The status code
35       */
36      int status();
37  
38      /**
39       * HTTP response headers.
40       * @return The headers
41       */
42      Map<String, List<String>> headers();
43  
44      /**
45       * HTTP response body.
46       * @return The body, as a UTF-8 string
47       */
48      String body();
49  
50      /**
51       * HTTP response body as bytes.
52       * @return The body, as byte array
53       */
54      byte[] bodyBytes();
55  
56      /**
57       * Simple implementation.
58       *
59       * @since 1.0
60       */
61      @Immutable
62      @EqualsAndHashCode(of = {"code", "hdrs", "content"})
63      @Loggable(Loggable.DEBUG)
64      final class Simple implements MkAnswer {
65          /**
66           * The Charset to use.
67           */
68          private static final Charset CHARSET = StandardCharsets.UTF_8;
69  
70          /**
71           * Encapsulated response.
72           */
73          private final transient int code;
74  
75          /**
76           * Headers.
77           */
78          private final transient Array<Map.Entry<String, String>> hdrs;
79  
80          /**
81           * Content received.
82           */
83          @Immutable.Array
84          private final transient byte[] content;
85  
86          /**
87           * Public ctor.
88           * @param body Body of HTTP response
89           */
90          public Simple(final String body) {
91              this(HttpURLConnection.HTTP_OK, body);
92          }
93  
94          /**
95           * Public ctor (with empty HTTP body).
96           * @param status HTTP status
97           * @since 1.9
98           */
99          public Simple(final int status) {
100             this(status, "");
101         }
102 
103         /**
104          * Public ctor.
105          * @param status HTTP status
106          * @param body Body of HTTP response
107          */
108         public Simple(final int status, final String body) {
109             this(
110                 status, new Array<Map.Entry<String, String>>(),
111                 body.getBytes(MkAnswer.Simple.CHARSET)
112             );
113         }
114 
115         /**
116          * Public ctor.
117          * @param status HTTP status
118          * @param headers HTTP headers
119          * @param body Body of HTTP response
120          */
121         public Simple(final int status,
122             final Iterable<Map.Entry<String, String>> headers,
123             final byte[] body) {
124             this.code = status;
125             this.hdrs = new Array<>(headers);
126             this.content = body.clone();
127         }
128 
129         @Override
130         public int status() {
131             return this.code;
132         }
133 
134         @Override
135         @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops")
136         public Map<String, List<String>> headers() {
137             final ConcurrentMap<String, List<String>> map =
138                 new ConcurrentHashMap<>(0);
139             for (final Map.Entry<String, String> header : this.hdrs) {
140                 map.putIfAbsent(header.getKey(), new LinkedList<String>());
141                 map.get(header.getKey()).add(header.getValue());
142             }
143             return map;
144         }
145 
146         @Override
147         public String body() {
148             return new String(this.content, MkAnswer.Simple.CHARSET);
149         }
150 
151         @Override
152         public byte[] bodyBytes() {
153             return this.content.clone();
154         }
155 
156         @Override
157         public String toString() {
158             final StringBuilder text = new StringBuilder(0)
159                 .append(this.code).append('\n');
160             for (final Map.Entry<String, String> header : this.hdrs) {
161                 text.append(
162                     Logger.format(
163                         "%s: %s\n",
164                         header.getKey(),
165                         header.getValue()
166                     )
167                 );
168             }
169             return text.append('\n')
170                 .append(new RequestBody.Printable(this.content))
171                 .toString();
172         }
173 
174         /**
175          * Make a copy of this answer, with an extra header.
176          * @param name Name of the header
177          * @param value ImmutableHeader value
178          * @return New answer
179          */
180         public MkAnswer.Simple withHeader(final String name,
181             final String value) {
182             return new MkAnswer.Simple(
183                 this.code,
184                 this.hdrs.with(new ImmutableHeader(name, value)),
185                 this.content
186             );
187         }
188 
189         /**
190          * Make a copy of this answer, with another status code.
191          * @param status Status code
192          * @return New answer
193          */
194         public MkAnswer.Simple withStatus(final int status) {
195             return new MkAnswer.Simple(
196                 status,
197                 this.hdrs,
198                 this.content
199             );
200         }
201 
202         /**
203          * Make a copy of this answer, with another body.
204          * @param body Body
205          * @return New answer
206          */
207         public MkAnswer.Simple withBody(final String body) {
208             return new MkAnswer.Simple(
209                 this.code,
210                 this.hdrs,
211                 body.getBytes(MkAnswer.Simple.CHARSET)
212             );
213         }
214 
215         /**
216          * Make a copy of this answer, with another body.
217          * @param body Body
218          * @return New answer
219          */
220         public MkAnswer.Simple withBody(final byte[] body) {
221             return new MkAnswer.Simple(this.code, this.hdrs, body);
222         }
223     }
224 
225 }