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.entity;
018
019import static org.apache.juneau.commons.utils.IoUtils.*;
020import static org.apache.juneau.commons.utils.Shorts.*;
021
022import java.io.*;
023import java.util.*;
024
025import org.apache.juneau.commons.io.*;
026import org.apache.juneau.http.*;
027import org.apache.juneau.http.classic.header.*;
028
029/**
030 * A streamed, non-repeatable entity that obtains its content from an {@link Reader}.
031 *
032 * <h5 class='section'>See Also:</h5><ul>
033 *    <li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestCommon">juneau-rest-common</a>
034 * </ul>
035 */
036@SuppressWarnings({
037   "java:S1192", // Duplicated literals (argument/property names) read more clearly inline than as constants
038   "java:S9365", // Copy ctor delegates to super; remaining fields are content caches defaulted empty by design.
039   "resource" // Resource management handled externally
040})
041public class ReaderEntity extends BasicHttpEntity<ReaderEntity> {
042
043   private byte[] byteCache;
044   private String stringCache;
045
046   /**
047    * Constructor.
048    */
049   public ReaderEntity() {}
050
051   /**
052    * Constructor.
053    *
054    * @param contentType The entity content type.  Can be <jk>null</jk> to omit an explicit <c>Content-Type</c> header.
055    * @param content The entity contents.  Can be <jk>null</jk>, in which case a {@link NullPointerException} is thrown when the entity's content is read.
056    */
057   public ReaderEntity(ContentType contentType, Reader content) {
058      super(contentType, content);
059   }
060
061   /**
062    * Copy constructor.
063    *
064    * @param copyFrom The bean being copied.  Must not be <jk>null</jk>.
065    */
066   protected ReaderEntity(ReaderEntity copyFrom) {
067      super(copyFrom);
068   }
069
070   @Override /* Overridden from AbstractHttpEntity */
071   public byte[] asBytes() throws IOException {
072      if (isCached() && byteCache == null)
073         byteCache = readBytes(content());
074      if (nn(byteCache))
075         return cp(byteCache);
076      return readBytes(content());
077   }
078
079   @Override /* Overridden from AbstractHttpEntity */
080   public String asString() throws IOException {
081      if (isCached() && stringCache == null)
082         stringCache = read(content(), getMaxLength());
083      if (nn(stringCache))
084         return stringCache;
085      return read(content());
086   }
087
088   @Override /* Overridden from BasicHttpEntity */
089   public ReaderEntity copy() {
090      return new ReaderEntity(this);
091   }
092
093   @Override /* Overridden from HttpEntity */
094   public InputStream getContent() throws IOException {
095      if (isCached())
096         return new ByteArrayInputStream(asBytes());
097      return new ReaderInputStream(content(), getCharset());
098   }
099
100   @Override /* Overridden from HttpEntity */
101   public long getContentLength() {
102      if (isCached())
103         return asSafeBytes().length;
104      return super.getContentLength();
105   }
106
107   @Override /* Overridden from HttpEntity */
108   public boolean isRepeatable() { return isCached(); }
109
110   @Override /* Overridden from HttpEntity */
111   public boolean isStreaming() { return ! isCached(); }
112
113   @Override /* Overridden from BasicHttpEntity */
114   public ReaderEntity unmodifiable() {
115      return this instanceof UnmodifiableBean ? this : new Unmodifiable(this);
116   }
117
118   /**
119    * Writes bytes from the {@code InputStream} this entity was constructed
120    * with to an {@code OutputStream}.  The content length
121    * determines how many bytes are written.  If the length is unknown ({@code -1}), the
122    * stream will be completely consumed (to the end of the stream).
123    */
124   @Override
125   public void writeTo(OutputStream out) throws IOException {
126      reqnn("out", out);
127
128      if (isCached()) {
129         out.write(asBytes());
130      } else {
131         var osw = new OutputStreamWriter(out, getCharset());
132         pipe(content(), osw);
133         osw.flush();
134      }
135      out.flush();
136   }
137
138   @Override /* Overridden from Object */
139   public boolean equals(Object o) {
140      // Leaf-type gate: keeps this entity from comparing equal to a different leaf with equal base content, while
141      // preserving content equality (ReaderEntity.Unmodifiable IS-A ReaderEntity, so bean.equals(bean.unmodifiable()) still holds).
142      // byteCache/stringCache are memoized derivations of the inherited content/charset and are excluded from equality.
143      return o instanceof ReaderEntity && super.equals(o);
144   }
145
146   @Override /* Overridden from Object */
147   public int hashCode() {
148      // Fold in the leaf type so distinct leaves land in different buckets; stable across ReaderEntity and its
149      // Unmodifiable snapshot (both report ReaderEntity.class), keeping the equals/hashCode contract intact.
150      return h(ReaderEntity.class, super.hashCode());
151   }
152
153   private Reader content() {
154      return Objects.requireNonNull(contentOrElse((Reader)null), "Reader is null.");
155   }
156
157   /**
158    * Unmodifiable point-in-time snapshot of the enclosing {@link ReaderEntity}.
159    *
160    * <p>
161    * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through
162    * {@code modify(...)}, this single override freezes the entire mutation surface.
163    */
164   public static class Unmodifiable extends ReaderEntity implements UnmodifiableBean {
165
166      /**
167       * Constructor.
168       *
169       * @param copyFrom The entity to snapshot.  Must not be <jk>null</jk>.
170       */
171      @SuppressWarnings({
172         "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans.
173      })
174      protected Unmodifiable(ReaderEntity copyFrom) {
175         super(copyFrom);
176         freeze();
177      }
178
179      @Override /* Overridden from BasicHttpEntity */
180      protected ReaderEntity modify(Runnable mutation) {
181         throw uoex("Bean is unmodifiable.");
182      }
183   }
184}