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}