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