/src/main/java/org/thymeleaf/engine/ThrottledTemplateWriterWriterAdapter.java

http://github.com/thymeleaf/thymeleaf · Java · 332 lines · 235 code · 64 blank · 33 comment · 49 complexity · 42be8c4c3240841930b18b6fd38ba7ce MD5 · raw file

  1. /*
  2. * =============================================================================
  3. *
  4. * Copyright (c) 2011-2018, The THYMELEAF team (http://www.thymeleaf.org)
  5. *
  6. * Licensed under the Apache License, Version 2.0 (the "License");
  7. * you may not use this file except in compliance with the License.
  8. * You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing, software
  13. * distributed under the License is distributed on an "AS IS" BASIS,
  14. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  15. * See the License for the specific language governing permissions and
  16. * limitations under the License.
  17. *
  18. * =============================================================================
  19. */
  20. package org.thymeleaf.engine;
  21. import java.io.IOException;
  22. import java.io.Writer;
  23. import java.util.Arrays;
  24. import org.thymeleaf.exceptions.TemplateOutputException;
  25. /**
  26. *
  27. * @author Daniel Fernández
  28. *
  29. * @since 3.0.0
  30. *
  31. */
  32. final class ThrottledTemplateWriterWriterAdapter
  33. extends Writer
  34. implements ThrottledTemplateWriter.IThrottledTemplateWriterAdapter {
  35. // Given we will be directly writing chars we will use a 256-char buffer as a sensible, approximate
  36. // measure of the amount of overflow we will need, given the only influencing factor for us is
  37. // the size of the structures being written to this writer (elements, texts, etc.)
  38. private static int OVERFLOW_BUFFER_INCREMENT = 256;
  39. private final String templateName;
  40. private final TemplateFlowController flowController;
  41. private Writer writer;
  42. private char[] overflow;
  43. private int overflowSize;
  44. private int maxOverflowSize;
  45. private int overflowGrowCount;
  46. private boolean unlimited;
  47. private int limit;
  48. private int writtenCount;
  49. ThrottledTemplateWriterWriterAdapter(final String templateName, final TemplateFlowController flowController) {
  50. super();
  51. this.templateName = templateName;
  52. this.flowController = flowController;
  53. this.overflow = null;
  54. this.overflowSize = 0;
  55. this.maxOverflowSize = 0;
  56. this.overflowGrowCount = 0;
  57. this.unlimited = false;
  58. this.limit = 0;
  59. this.writtenCount = 0;
  60. this.flowController.stopProcessing = true;
  61. }
  62. void setWriter(final Writer writer) {
  63. this.writer = writer;
  64. this.writtenCount = 0;
  65. }
  66. public boolean isOverflown() {
  67. return this.overflowSize > 0;
  68. }
  69. public boolean isStopped() {
  70. return this.limit == 0;
  71. }
  72. public int getWrittenCount() {
  73. return this.writtenCount;
  74. }
  75. public int getMaxOverflowSize() {
  76. return this.maxOverflowSize;
  77. }
  78. public int getOverflowGrowCount() {
  79. return this.overflowGrowCount;
  80. }
  81. public void allow(final int limit) {
  82. if (limit == Integer.MAX_VALUE || limit < 0) {
  83. this.unlimited = true;
  84. this.limit = -1;
  85. } else {
  86. this.unlimited = false;
  87. this.limit = limit;
  88. }
  89. this.flowController.stopProcessing = (this.limit == 0);
  90. if (this.overflowSize == 0 || this.limit == 0) {
  91. return;
  92. }
  93. try {
  94. if (this.unlimited || this.limit > this.overflowSize) {
  95. this.writer.write(this.overflow, 0, this.overflowSize);
  96. if (!this.unlimited) {
  97. this.limit -= this.overflowSize;
  98. }
  99. this.writtenCount += this.overflowSize;
  100. this.overflowSize = 0;
  101. return;
  102. }
  103. this.writer.write(this.overflow, 0, this.limit);
  104. if (this.limit < this.overflowSize) {
  105. System.arraycopy(this.overflow, this.limit, this.overflow, 0, this.overflowSize - this.limit);
  106. }
  107. this.overflowSize -= this.limit;
  108. this.writtenCount += this.limit;
  109. this.limit = 0;
  110. this.flowController.stopProcessing = true;
  111. } catch (final IOException e) {
  112. throw new TemplateOutputException(
  113. "Exception while trying to write overflowed buffer in throttled template", this.templateName, -1, -1, e);
  114. }
  115. }
  116. @Override
  117. public void write(final int c) throws IOException {
  118. if (this.limit == 0) {
  119. overflow(c);
  120. return;
  121. }
  122. this.writer.write(c);
  123. if (!this.unlimited) {
  124. this.limit--;
  125. }
  126. this.writtenCount++;
  127. if (this.limit == 0) {
  128. this.flowController.stopProcessing = true;
  129. }
  130. }
  131. @Override
  132. public void write(final String str) throws IOException {
  133. final int len = str.length();
  134. if (this.limit == 0) {
  135. overflow(str, 0, len);
  136. return;
  137. }
  138. if (this.unlimited || this.limit > len) {
  139. this.writer.write(str, 0, len);
  140. if (!this.unlimited) {
  141. this.limit -= len;
  142. }
  143. this.writtenCount += len;
  144. return;
  145. }
  146. this.writer.write(str, 0, this.limit);
  147. if (this.limit < len) {
  148. overflow(str, this.limit, (len - this.limit));
  149. }
  150. this.writtenCount += this.limit;
  151. this.limit = 0;
  152. this.flowController.stopProcessing = true;
  153. }
  154. @Override
  155. public void write(final String str, final int off, final int len) throws IOException {
  156. if (this.limit == 0) {
  157. overflow(str, off, len);
  158. return;
  159. }
  160. if (this.unlimited || this.limit > len) {
  161. this.writer.write(str, off, len);
  162. if (!this.unlimited) {
  163. this.limit -= len;
  164. }
  165. this.writtenCount += len;
  166. return;
  167. }
  168. this.writer.write(str, off, this.limit);
  169. if (this.limit < len) {
  170. overflow(str, off + this.limit, (len - this.limit));
  171. }
  172. this.writtenCount += this.limit;
  173. this.limit = 0;
  174. this.flowController.stopProcessing = true;
  175. }
  176. @Override
  177. public void write(final char[] cbuf) throws IOException {
  178. final int len = cbuf.length;
  179. if (this.limit == 0) {
  180. overflow(cbuf, 0, len);
  181. return;
  182. }
  183. if (this.unlimited || this.limit > len) {
  184. this.writer.write(cbuf, 0, len);
  185. if (!this.unlimited) {
  186. this.limit -= len;
  187. }
  188. this.writtenCount += len;
  189. return;
  190. }
  191. this.writer.write(cbuf, 0, this.limit);
  192. if (this.limit < len) {
  193. overflow(cbuf, this.limit, (len - this.limit));
  194. }
  195. this.writtenCount += this.limit;
  196. this.limit = 0;
  197. this.flowController.stopProcessing = true;
  198. }
  199. @Override
  200. public void write(final char[] cbuf, final int off, final int len) throws IOException {
  201. if (this.limit == 0) {
  202. overflow(cbuf, off, len);
  203. return;
  204. }
  205. if (this.unlimited || this.limit > len) {
  206. this.writer.write(cbuf, off, len);
  207. if (!this.unlimited) {
  208. this.limit -= len;
  209. }
  210. this.writtenCount += len;
  211. return;
  212. }
  213. this.writer.write(cbuf, off, this.limit);
  214. if (this.limit < len) {
  215. overflow(cbuf, off + this.limit, (len - this.limit));
  216. }
  217. this.writtenCount += this.limit;
  218. this.limit = 0;
  219. this.flowController.stopProcessing = true;
  220. }
  221. private void overflow(final int c) {
  222. ensureOverflowCapacity(1);
  223. this.overflow[this.overflowSize] = (char)c;
  224. this.overflowSize++;
  225. if (this.overflowSize > this.maxOverflowSize) {
  226. this.maxOverflowSize = this.overflowSize;
  227. }
  228. }
  229. private void overflow(final String str, final int off, final int len) {
  230. ensureOverflowCapacity(len);
  231. str.getChars(off, off + len, this.overflow, this.overflowSize);
  232. this.overflowSize += len;
  233. if (this.overflowSize > this.maxOverflowSize) {
  234. this.maxOverflowSize = this.overflowSize;
  235. }
  236. }
  237. private void overflow(final char[] cbuf, final int off, final int len) {
  238. ensureOverflowCapacity(len);
  239. System.arraycopy(cbuf, off, this.overflow, this.overflowSize, len);
  240. this.overflowSize += len;
  241. if (this.overflowSize > this.maxOverflowSize) {
  242. this.maxOverflowSize = this.overflowSize;
  243. }
  244. }
  245. private void ensureOverflowCapacity(final int len) {
  246. if (this.overflow == null) {
  247. this.overflow = new char[((len / OVERFLOW_BUFFER_INCREMENT) + 1) * OVERFLOW_BUFFER_INCREMENT];
  248. return;
  249. }
  250. final int targetLen = this.overflowSize + len;
  251. if (this.overflow.length < targetLen) {
  252. this.overflow = Arrays.copyOf(this.overflow, ((targetLen / OVERFLOW_BUFFER_INCREMENT) + 1) * OVERFLOW_BUFFER_INCREMENT);
  253. this.overflowGrowCount++;
  254. }
  255. }
  256. @Override
  257. public void flush() throws IOException {
  258. // No need to control overflow here. The fact that this has overflow will be used as a flag to determine
  259. // that further write operations are actually needed by means of the isOverflown() method.
  260. this.writer.flush();
  261. }
  262. @Override
  263. public void close() throws IOException {
  264. // This will normally be NEVER called, as Thymeleaf will not call close() on its Writers/OutputStreams
  265. // (only flush() is guaranteed to be called at the end).
  266. this.writer.close();
  267. }
  268. }