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.*;
023
024import org.apache.juneau.commons.io.*;
025import org.apache.juneau.http.*;
026import org.apache.juneau.http.classic.header.*;
027
028/**
029 * A self contained, repeatable entity that obtains its content from a {@link String}.
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 StringEntity extends BasicHttpEntity<StringEntity> {
041
042   private static final String EMPTY = "";
043   private byte[] byteCache;
044
045   /**
046    * Constructor.
047    */
048   public StringEntity() {}
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> (treated as an empty string).
055    */
056   public StringEntity(ContentType contentType, String 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 StringEntity(StringEntity copyFrom) {
066      super(copyFrom);
067   }
068
069   @Override /* Overridden from AbstractHttpEntity */
070   public byte[] asBytes() throws IOException {
071      if (isCached() && byteCache == null)
072         byteCache = content().getBytes(getCharset());
073      if (nn(byteCache))
074         return cp(byteCache);
075      return content().getBytes(getCharset());
076   }
077
078   @Override /* Overridden from AbstractHttpEntity */
079   public String asString() throws IOException {
080      return content();
081   }
082
083   @Override /* Overridden from BasicHttpEntity */
084   public StringEntity copy() {
085      return new StringEntity(this);
086   }
087
088   @Override /* Overridden from HttpEntity */
089   public InputStream getContent() throws IOException {
090      if (isCached())
091         return new ByteArrayInputStream(asBytes());
092      return new ReaderInputStream(new StringReader(content()), getCharset());
093   }
094
095   @Override /* Overridden from HttpEntity */
096   public long getContentLength() {
097      if (isCached())
098         return asSafeBytes().length;
099      long l = super.getContentLength();
100      if (l != -1 || isSupplied())
101         return l;
102      String s = content();
103      if (getCharset() == UTF8)
104         for (var i = 0; i < s.length(); i++)
105            if (s.charAt(i) > 127)
106               return -1;
107      return s.length();
108   }
109
110   @Override /* Overridden from HttpEntity */
111   public boolean isRepeatable() { return true; }
112
113   @Override /* Overridden from HttpEntity */
114   public boolean isStreaming() { return false; }
115
116   @Override /* Overridden from BasicHttpEntity */
117   public StringEntity unmodifiable() {
118      return this instanceof UnmodifiableBean ? this : new Unmodifiable(this);
119   }
120
121   @Override /* Overridden from HttpEntity */
122   public void writeTo(OutputStream out) throws IOException {
123      reqnn("out", out);
124      if (isCached()) {
125         out.write(asBytes());
126      } else {
127         var osw = new OutputStreamWriter(out, getCharset());
128         osw.write(content());
129         osw.flush();
130      }
131   }
132
133   @Override /* Overridden from Object */
134   public boolean equals(Object o) {
135      // Leaf-type gate: keeps this entity from comparing equal to a different leaf with equal base content, while
136      // preserving content equality (StringEntity.Unmodifiable IS-A StringEntity, so bean.equals(bean.unmodifiable()) still holds).
137      // byteCache is a memoized derivation of the inherited content/charset and is intentionally excluded from equality.
138      return o instanceof StringEntity && super.equals(o);
139   }
140
141   @Override /* Overridden from Object */
142   public int hashCode() {
143      // Fold in the leaf type so distinct leaves land in different buckets; stable across StringEntity and its
144      // Unmodifiable snapshot (both report StringEntity.class), keeping the equals/hashCode contract intact.
145      return h(StringEntity.class, super.hashCode());
146   }
147
148   private String content() {
149      return contentOrElse(EMPTY);
150   }
151
152   /**
153    * Unmodifiable point-in-time snapshot of the enclosing {@link StringEntity}.
154    *
155    * <p>
156    * Its only behavioral override is {@link #modify(Runnable)}, which throws — because all mutation is funneled through
157    * {@code modify(...)}, this single override freezes the entire mutation surface.
158    */
159   public static class Unmodifiable extends StringEntity implements UnmodifiableBean {
160
161      /**
162       * Constructor.
163       *
164       * @param copyFrom The entity to snapshot.  Must not be <jk>null</jk>.
165       */
166      @SuppressWarnings({
167         "java:S1699" // Paradigm intentionally calls the overridable freeze() from the ctor to deep-freeze sub-beans.
168      })
169      protected Unmodifiable(StringEntity copyFrom) {
170         super(copyFrom);
171         freeze();
172      }
173
174      @Override /* Overridden from BasicHttpEntity */
175      protected StringEntity modify(Runnable mutation) {
176         throw uoex("Bean is unmodifiable.");
177      }
178   }
179}