All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.wicket.portlet.ResponseState Maven / Gradle / Ivy

There is a newer version: 9.19.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.wicket.portlet;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayReader;
import java.io.CharArrayWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;

import javax.portlet.MimeResponse;
import javax.portlet.PortletRequest;
import javax.portlet.PortletResponse;
import javax.portlet.ResourceResponse;
import javax.servlet.ServletContext;
import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.Cookie;

/**
 * Temporarily holds the current state of a Wicket response when invoked from
 * WicketPortlet: buffer, headers, state and the redirect location to be
 * processed afterwards within WicketPortlet
 *
 * @author Ate Douma
 * @author Peter Pastrnak
 */
public class ResponseState {
	private static final String AJAX_LOCATION_HEADER_NAME = "Ajax-Location";
	private static final int INITIAL_BUFFER_SIZE = 8 * 1024;
	private static final int MEMORY_BUFFER_SIZE = 64 * 1024;
	private static final String DEFAULT_CHARSET = "UTF-8";
	private static final AtomicLong index = new AtomicLong();

	private final boolean isActionResponse;
	private final boolean isEventResponse;
	private final boolean isRenderResponse;
	private final boolean isResourceResponse;
	private final boolean isMimeResponse;
	private final boolean isStateAwareResponse;
	private final Locale defaultLocale;
	private final PortletResponse response;
	private boolean flushed;
	private File responseBufferFolder;

	private ByteOutputBuffer byteOutputBuffer;
	private CharOutputBuffer charOutputBuffer;
	private ServletOutputStream outputStream;
	private PrintWriter printWriter;
	private HashMap> headers;
	private ArrayList cookies;
	private boolean committed;
	private boolean closed;
	private boolean hasStatus;
	private boolean hasError;
	private Locale locale;
	private boolean setContentTypeAfterEncoding;
	private String characterEncoding;
	private int contentLength = -1;
	private String contentType;
	private int statusCode;

	/**
	 * FIXME javadoc
	 *
	 * Stores the effective wicket url which is used by {@link WicketPortlet} in
	 * the view phase to request a render from wicket core.
	 *
	 * @see IRequestCycleSettings#REDIRECT_TO_RENDER
	 * @see WicketFilterPortletHelper#initFilter
	 */
	private String redirectLocation;

	public ResponseState(PortletRequest request, PortletResponse response, File responseBufferFolder) {
		this.responseBufferFolder = responseBufferFolder;
		String lifecyclePhase = (String) request.getAttribute(PortletRequest.LIFECYCLE_PHASE);
		isActionResponse = PortletRequest.ACTION_PHASE.equals(lifecyclePhase);
		isEventResponse = PortletRequest.EVENT_PHASE.equals(lifecyclePhase);
		isRenderResponse = PortletRequest.RENDER_PHASE.equals(lifecyclePhase);
		isResourceResponse = PortletRequest.RESOURCE_PHASE.equals(lifecyclePhase);
		isStateAwareResponse = isActionResponse || isEventResponse;
		isMimeResponse = isRenderResponse || isResourceResponse;
		this.response = response;
		defaultLocale = isMimeResponse ? ((MimeResponse) response).getLocale() : null;
	}

	private ArrayList getHeaderList(String name, boolean create) {
		if (headers == null) {
			headers = new HashMap>();
		}
		ArrayList headerList = headers.get(name);
		if (headerList == null && create) {
			headerList = new ArrayList();
			headers.put(name, headerList);
		}
		return headerList;
	}

	private void failIfCommitted() {
		if (committed) {
			throw new IllegalStateException("Response has been already committed.");
		}
	}

	public boolean isActionResponse() {
		return isActionResponse;
	}

	public boolean isEventResponse() {
		return isEventResponse;
	}

	public boolean isRenderResponse() {
		return isRenderResponse;
	}

	public boolean isResourceResponse() {
		return isResourceResponse;
	}

	public boolean isMimeResponse() {
		return isMimeResponse;
	}

	public boolean isStateAwareResponse() {
		return isStateAwareResponse;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#addCookie(javax.servlet
	 * .http.Cookie)
	 */
	public void addCookie(Cookie cookie) {
		failIfCommitted();

		if (cookies == null) {
			cookies = new ArrayList();
		}
		cookies.add(cookie);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#addDateHeader(java.lang
	 * .String, long)
	 */
	public void addDateHeader(String name, long date) {
		addHeader(name, Long.toString(date));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String,
	 * java.lang.String)
	 */
	public void addHeader(String name, String value) {
		if (isMimeResponse) {
			failIfCommitted();
			getHeaderList(name, true).add(value);
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#addIntHeader(java.lang.
	 * String, int)
	 */
	public void addIntHeader(String name, int value) {
		addHeader(name, Integer.toString(value));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#containsHeader(java.lang
	 * .String)
	 */
	public boolean containsHeader(String name) {
		// Note: Portlet Spec 2.0 demands this to always return false...
		return isMimeResponse && getHeaderList(name, false) != null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int,
	 * java.lang.String)
	 */
	public void sendError(int errorCode, String errorMessage) throws IOException {
		failIfCommitted();
		committed = true;
		closed = true;
		hasError = true;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int)
	 */
	public void sendError(int errorCode) throws IOException {
		sendError(errorCode, null);
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#sendRedirect(java.lang.
	 * String)
	 */
	public void sendRedirect(String redirectLocation) throws IOException {
		if (isActionResponse || isMimeResponse) {
			failIfCommitted();
			this.closed = true;
			this.committed = true;
			this.redirectLocation = redirectLocation;
		}
	}

	public String getRedirectLocation() {
		return redirectLocation;
	}

	public String getAjaxRedirectLocation() {
		ArrayList values = getHeaderList(AJAX_LOCATION_HEADER_NAME, false);
		return ((values != null) && (!values.isEmpty())) ? values.get(values.size() - 1) : null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#setDateHeader(java.lang
	 * .String, long)
	 */
	public void setDateHeader(String name, long date) {
		setHeader(name, Long.toString(date));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#setHeader(java.lang.String,
	 * java.lang.String)
	 */
	public void setHeader(String name, String value) {
		if (isMimeResponse && !committed) {
			ArrayList headerList = getHeaderList(name, true);
			headerList.clear();
			headerList.add(value);

			if (AJAX_LOCATION_HEADER_NAME.equalsIgnoreCase(name)) {
				redirectLocation = value;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.http.HttpServletResponseWrapper#setIntHeader(java.lang.
	 * String, int)
	 */
	public void setIntHeader(String name, int value) {
		setHeader(name, Integer.toString(value));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int,
	 * java.lang.String)
	 */
	public void setStatus(int statusCode, String message) {
		throw new UnsupportedOperationException("This method is deprecated and no longer supported");
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int)
	 */
	public void setStatus(int statusCode) {
		if (!committed) {
			this.statusCode = statusCode;
			hasStatus = true;
			resetBuffer();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#flushBuffer()
	 */
	public void flushBuffer() throws IOException {
		if (isMimeResponse && !closed) {
			committed = true;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getBufferSize()
	 */
	public int getBufferSize() {
		return isMimeResponse ? Integer.MAX_VALUE : 0;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getCharacterEncoding()
	 */
	public String getCharacterEncoding() {
		return isMimeResponse ? characterEncoding != null ? characterEncoding : "ISO-8859-1" : null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getContentType()
	 */
	public String getContentType() {
		return isMimeResponse ? contentType : null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getLocale()
	 */
	public Locale getLocale() {
		return isMimeResponse ? locale != null ? locale : defaultLocale : null;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getOutputStream()
	 */
	public ServletOutputStream getOutputStream() throws IOException {
		if (isStateAwareResponse) {
			// Portlet Spec 2.0 requires Portlet Container to supply a "no-op"
			// OutputStream object
			// so delegate back to current PortletServletResponseWrapper to
			// return that one
			return null;
		}
		if (outputStream == null) {
			if (printWriter != null) {
				throw new IllegalStateException("getWriter() has already been called on this response");
			}
			byteOutputBuffer = new ByteOutputBuffer();
			outputStream = new ServletOutputStream() {
				@Override
				public void write(int b) throws IOException {
					if (!closed) {
						byteOutputBuffer.write(b);
						if ((contentLength) > -1 && (byteOutputBuffer.size() >= contentLength)) {
							committed = true;
							closed = true;
						}
					}
				}

				@Override
				public boolean isReady() {
					return true;
				}

				@Override
				public void setWriteListener(WriteListener writeListener) {
				}
			};
		}
		return outputStream;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#getWriter()
	 */
	public PrintWriter getWriter() throws IOException {
		if (isStateAwareResponse) {
			// Portlet Spec 2.0 requires Portlet Container to supply a "no-op"
			// PrintWriter object
			// so delegate back to current PortletServletResponseWrapper to
			// return that one
			return null;
		}
		if (printWriter == null) {
			if (outputStream != null) {
				throw new IllegalStateException("getOutputStream() has already been called on this response");
			}
			charOutputBuffer = new CharOutputBuffer();
			printWriter = new PrintWriter(charOutputBuffer);
		}
		return printWriter;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#isCommitted()
	 */
	public boolean isCommitted() {
		return isMimeResponse && committed;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#reset()
	 */
	public void reset() {
		resetBuffer(); // fails if committed
		headers = null;
		cookies = null;
		hasStatus = false;
		contentLength = -1;
		if (printWriter == null) {
			contentType = null;
			characterEncoding = null;
			locale = null;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#resetBuffer()
	 */
	public void resetBuffer() {
		failIfCommitted();
		if (outputStream != null) {
			try {
				outputStream.flush();
			}
			catch (Exception e) {
			}
			byteOutputBuffer.reset();
		}
		else if (printWriter != null) {
			printWriter.flush();
			charOutputBuffer.reset();
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#setBufferSize(int)
	 */
	public void setBufferSize(int size) {
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.ServletResponseWrapper#setCharacterEncoding(java.lang.String
	 * )
	 */
	public void setCharacterEncoding(String charset) {
		if (isResourceResponse && charset != null && printWriter == null) {
			failIfCommitted();
			characterEncoding = charset;
			setContentTypeAfterEncoding = false;
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#setContentLength(int)
	 */
	public void setContentLength(int len) {
		if (isResourceResponse && printWriter == null && len > 0) {
			failIfCommitted();

			contentLength = len;
			if (outputStream != null) {
				try {
					outputStream.flush();
				}
				catch (Exception e) {
				}
			}

			if ((!closed) && (byteOutputBuffer != null) && (byteOutputBuffer.size() >= len)) {
				committed = true;
				closed = true;
			}
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see
	 * javax.servlet.ServletResponseWrapper#setContentType(java.lang.String)
	 */
	public void setContentType(String type) {
		if (isMimeResponse) {
			failIfCommitted();

			contentType = type;
			setContentTypeAfterEncoding = false;
			if (printWriter == null) {
				// TODO: parse possible encoding for better return value from
				// getCharacterEncoding()
			}
		}
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see javax.servlet.ServletResponseWrapper#setLocale(java.util.Locale)
	 */
	public void setLocale(Locale locale) {
		if (isResourceResponse) {
			failIfCommitted();
			this.locale = locale;
		}
	}

	public void clear() {
		if (printWriter != null) {
			printWriter.close();
			charOutputBuffer.reset();
			printWriter = null;
		}

		if (outputStream != null) {
			try {
				outputStream.close();
			}
			catch (IOException e) {
			}
			outputStream = null;
			byteOutputBuffer.reset();
		}

		headers = null;
		cookies = null;
		committed = false;
		hasStatus = false;
		hasError = false;
		locale = null;
		setContentTypeAfterEncoding = false;
		closed = false;
		characterEncoding = null;
		contentLength = -1;
		contentType = null;
		statusCode = 0;
		redirectLocation = null;
	}

	public void flushAndClose() throws IOException {
		if (flushed) {
			throw new IllegalStateException("Response has been already flushed and closed.");
		}
		flushed = true;

		if (cookies != null) {
			for (Cookie cookie : cookies) {
				response.addProperty(cookie);
			}
			cookies = null;
		}

		if (isMimeResponse) {
			MimeResponse mimeResponse = (MimeResponse) response;
			ResourceResponse resourceResponse = isResourceResponse ? (ResourceResponse) response : null;

			if (locale != null) {
				try {
					resourceResponse.setLocale(locale);
				}
				catch (UnsupportedOperationException usoe) {
					// TODO: temporary "fix" for JBoss Portal which doesn't
					// yet
					// support this
					// (although required by the Portlet API 2.0!)
				}
			}

			if (contentType != null) {
				if (characterEncoding != null) {
					if (setContentTypeAfterEncoding) {
						resourceResponse.setCharacterEncoding(characterEncoding);
						resourceResponse.setContentType(contentType);
					}
					else {
						resourceResponse.setContentType(contentType);
						resourceResponse.setCharacterEncoding(characterEncoding);
					}
				}
				else {
					mimeResponse.setContentType(contentType);
				}
			}
			else if (characterEncoding != null) {
				resourceResponse.setCharacterEncoding(characterEncoding);
			}

			if (headers != null) {
				for (Map.Entry> entry : headers.entrySet()) {
					for (String value : entry.getValue()) {
						mimeResponse.addProperty(entry.getKey(), value);
					}
				}
				headers = null;
			}

			if (isResourceResponse && hasStatus) {
				resourceResponse.setProperty(ResourceResponse.HTTP_STATUS_CODE, Integer.toString(statusCode));
			}

			if (isResourceResponse && contentLength > -1) {
				try {
					resourceResponse.setContentLength(contentLength);
				}
				catch (UnsupportedOperationException usoe) {
					// TODO: temporary "fix" for JBoss Portal which doesn't
					// yet
					// support this
					// (although required by the Portlet API 2.0!)
				}
			}
		}

		if (!hasError && !closed && redirectLocation == null && isMimeResponse) {
			MimeResponse mimeResponse = (MimeResponse) response;

			if (outputStream != null) {
				outputStream.flush();
				byteOutputBuffer.writeToAndClose(mimeResponse.getPortletOutputStream());
			}
			else if (printWriter != null) {
				printWriter.flush();
				charOutputBuffer.writeToAndClose(mimeResponse.getWriter());
			}
		}

		if (outputStream != null) {
			outputStream.close();
			outputStream = null;
		}
		if (printWriter != null) {
			printWriter.close();
			printWriter = null;
		}
	}

	private File getResponseBufferFolder() {
		if (responseBufferFolder == null) {
			ServletContext context = ThreadPortletContext.getServletContext();
			if (context != null) {
				responseBufferFolder = (File) context.getAttribute("javax.servlet.context.tempdir");
				if (responseBufferFolder != null) {
					responseBufferFolder = new File(responseBufferFolder, "wicket-response-buffers");
				}
			}

			if (responseBufferFolder == null) {
				try {
					responseBufferFolder = File.createTempFile("wicket-response-buffers", null).getParentFile();
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot create response buffer folder.", e);
				}
			}
			responseBufferFolder.mkdirs();
		}
		return responseBufferFolder;
	}

	private File getResponseBufferFile() {
		return new File(getResponseBufferFolder(), "response-buffer" + index.getAndIncrement());
	}

	private class CharOutputBuffer extends CharArrayWriter {
		private File file;

		private Writer fileWriter;

		public CharOutputBuffer() {
			super(INITIAL_BUFFER_SIZE);
		}

		private boolean useFile(int pos) {
			return (fileWriter != null) || (pos > MEMORY_BUFFER_SIZE);
		}

		private Writer getFileWriter() throws IOException {
			if (fileWriter == null) {
				file = getResponseBufferFile();
				fileWriter = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file), DEFAULT_CHARSET));
				fileWriter.write(buf, 0, count);
			}
			return fileWriter;
		}

		@Override
		public void write(int c) {
			if (useFile(count + 1)) {
				try {
					getFileWriter().write(c);
					count++;
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot write cache file.", e);
				}
			}
			else {
				super.write(c);
			}
		}

		@Override
		public void write(char[] c, int off, int len) {
			if (useFile(count + len)) {
				try {
					getFileWriter().write(c, off, len);
					count += len;
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot write cache file.", e);
				}
			}
			else {
				super.write(c, off, len);
			}
		}

		@Override
		public void write(String str, int off, int len) {
			if (useFile(count + 1)) {
				try {
					getFileWriter().write(str, off, len);
					count += len;
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot write cache file.", e);
				}
			}
			else {
				super.write(str, off, len);
			}
		}

		public void writeToAndClose(Writer writer) throws IOException {
			try {
				Reader reader;

				if (fileWriter != null) {
					try {
						fileWriter.close();
					}
					catch (Exception e) {
					}

					reader = new BufferedReader(new InputStreamReader(new FileInputStream(file), DEFAULT_CHARSET));
				}
				else {
					reader = new CharArrayReader(buf, 0, count);
				}

				try {
					int count;
					while ((count = reader.read(buf)) > 0) {
						writer.write(buf, 0, count);
					}
				}
				finally {
					if (reader != null) {
						try {
							reader.close();
						}
						catch (Exception e) {
						}
					}
				}
			}
			finally {
				buf = null;
				reset();
			}
		}

		@Override
		public void reset() {
			super.reset();

			if (fileWriter != null) {
				try {
					fileWriter.close();
					fileWriter = null;
				}
				catch (Exception e) {
				}
			}

			if (file != null) {
				try {
					file.delete();
					file = null;
				}
				catch (Exception e) {
				}
			}
		}

		@Override
		protected void finalize() throws Throwable {
			reset();
		}
	}

	private class ByteOutputBuffer extends ByteArrayOutputStream {
		private File file;

		private OutputStream fileOutputStream;

		public ByteOutputBuffer() {
			super(INITIAL_BUFFER_SIZE);
		}

		private boolean useFile(int pos) {
			return (fileOutputStream != null) || (pos > MEMORY_BUFFER_SIZE);
		}

		private OutputStream getFileWriter() throws IOException {
			if (fileOutputStream == null) {
				file = getResponseBufferFile();
				fileOutputStream = new BufferedOutputStream(new FileOutputStream(file));
				fileOutputStream.write(buf, 0, count);
			}
			return fileOutputStream;
		}

		@Override
		public void write(int b) {
			if (useFile(count + 1)) {
				try {
					getFileWriter().write(b);
					count++;
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot write cache file.", e);
				}
			}
			else {
				super.write(b);
			}
		}

		@Override
		public void write(byte[] b, int off, int len) {
			if (useFile(count + len)) {
				try {
					getFileWriter().write(b, off, len);
					count += len;
				}
				catch (IOException e) {
					throw new IllegalStateException("Cannot write cache file.", e);
				}
			}
			else {
				super.write(b, off, len);
			}
		}

		public void writeToAndClose(OutputStream outputStream) throws IOException {
			try {
				InputStream inputStream;

				if (fileOutputStream != null) {
					try {
						fileOutputStream.close();
					}
					catch (Exception e) {
					}

					inputStream = new BufferedInputStream(new FileInputStream(file));
				}
				else {
					inputStream = new ByteArrayInputStream(buf, 0, count);
				}

				try {
					int count;
					while ((count = inputStream.read(buf)) > 0) {
						outputStream.write(buf, 0, count);
					}
				}
				finally {
					if (inputStream != null) {
						try {
							inputStream.close();
						}
						catch (Exception e) {
						}
					}
				}
			}
			finally {
				buf = null;
				reset();
			}
		}

		@Override
		public void reset() {
			super.reset();

			if (fileOutputStream != null) {
				try {
					fileOutputStream.close();
					fileOutputStream = null;
				}
				catch (Exception e) {
				}
			}

			if (file != null) {
				try {
					file.delete();
					file = null;
				}
				catch (Exception e) {
				}
			}
		}

		@Override
		protected void finalize() throws Throwable {
			reset();
		}
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy