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}