summaryrefslogtreecommitdiffstats
path: root/core/tests/utillib
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2012-03-28 18:59:21 -0700
committerJeff Sharkey <jsharkey@android.com>2012-03-29 14:17:03 -0700
commitb14ad8cc8cb0ed774072b077694b21fd0a6f33be (patch)
treebec8c55ccb53b55213f0440879399974c8edd4f6 /core/tests/utillib
parente32a4ac8d39b96d965acccd1d3e9ef111c4cfa37 (diff)
downloadframeworks_base-b14ad8cc8cb0ed774072b077694b21fd0a6f33be.zip
frameworks_base-b14ad8cc8cb0ed774072b077694b21fd0a6f33be.tar.gz
frameworks_base-b14ad8cc8cb0ed774072b077694b21fd0a6f33be.tar.bz2
Migrate to shared MockWebServer.
Bug: 4726601 Change-Id: Ibbe71b5785a64bd2883c46b83e721fba9a25afa2
Diffstat (limited to 'core/tests/utillib')
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockResponse.java239
-rw-r--r--core/tests/utillib/src/coretestutils/http/MockWebServer.java426
-rw-r--r--core/tests/utillib/src/coretestutils/http/RecordedRequest.java93
3 files changed, 0 insertions, 758 deletions
diff --git a/core/tests/utillib/src/coretestutils/http/MockResponse.java b/core/tests/utillib/src/coretestutils/http/MockResponse.java
deleted file mode 100644
index 5b03e5f..0000000
--- a/core/tests/utillib/src/coretestutils/http/MockResponse.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed 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 coretestutils.http;
-
-import static coretestutils.http.MockWebServer.ASCII;
-
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.FileNotFoundException;
-import java.io.InputStream;
-import java.io.IOException;
-import java.io.UnsupportedEncodingException;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import android.util.Log;
-
-/**
- * A scripted response to be replayed by the mock web server.
- */
-public class MockResponse {
- private static final byte[] EMPTY_BODY = new byte[0];
- static final String LOG_TAG = "coretestutils.http.MockResponse";
-
- private String status = "HTTP/1.1 200 OK";
- private Map<String, String> headers = new HashMap<String, String>();
- private byte[] body = EMPTY_BODY;
- private boolean closeConnectionAfter = false;
- private String closeConnectionAfterHeader = null;
- private int closeConnectionAfterXBytes = -1;
- private int pauseConnectionAfterXBytes = -1;
- private File bodyExternalFile = null;
-
- public MockResponse() {
- addHeader("Content-Length", 0);
- }
-
- /**
- * Returns the HTTP response line, such as "HTTP/1.1 200 OK".
- */
- public String getStatus() {
- return status;
- }
-
- public MockResponse setResponseCode(int code) {
- this.status = "HTTP/1.1 " + code + " OK";
- return this;
- }
-
- /**
- * Returns the HTTP headers, such as "Content-Length: 0".
- */
- public List<String> getHeaders() {
- List<String> headerStrings = new ArrayList<String>();
- for (String header : headers.keySet()) {
- headerStrings.add(header + ": " + headers.get(header));
- }
- return headerStrings;
- }
-
- public MockResponse addHeader(String header, String value) {
- headers.put(header.toLowerCase(), value);
- return this;
- }
-
- public MockResponse addHeader(String header, long value) {
- return addHeader(header, Long.toString(value));
- }
-
- public MockResponse removeHeader(String header) {
- headers.remove(header.toLowerCase());
- return this;
- }
-
- /**
- * Returns true if the body should come from an external file, false otherwise.
- */
- private boolean bodyIsExternal() {
- return bodyExternalFile != null;
- }
-
- /**
- * Returns an input stream containing the raw HTTP payload.
- */
- public InputStream getBody() {
- if (bodyIsExternal()) {
- try {
- return new FileInputStream(bodyExternalFile);
- } catch (FileNotFoundException e) {
- Log.e(LOG_TAG, "File not found: " + bodyExternalFile.getAbsolutePath());
- }
- }
- return new ByteArrayInputStream(this.body);
- }
-
- public MockResponse setBody(File body) {
- addHeader("Content-Length", body.length());
- this.bodyExternalFile = body;
- return this;
- }
-
- public MockResponse setBody(byte[] body) {
- addHeader("Content-Length", body.length);
- this.body = body;
- return this;
- }
-
- public MockResponse setBody(String body) {
- try {
- return setBody(body.getBytes(ASCII));
- } catch (UnsupportedEncodingException e) {
- throw new AssertionError();
- }
- }
-
- /**
- * Sets the body as chunked.
- *
- * Currently chunked body is not supported for external files as bodies.
- */
- public MockResponse setChunkedBody(byte[] body, int maxChunkSize) throws IOException {
- addHeader("Transfer-encoding", "chunked");
-
- ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
- int pos = 0;
- while (pos < body.length) {
- int chunkSize = Math.min(body.length - pos, maxChunkSize);
- bytesOut.write(Integer.toHexString(chunkSize).getBytes(ASCII));
- bytesOut.write("\r\n".getBytes(ASCII));
- bytesOut.write(body, pos, chunkSize);
- bytesOut.write("\r\n".getBytes(ASCII));
- pos += chunkSize;
- }
- bytesOut.write("0\r\n".getBytes(ASCII));
- this.body = bytesOut.toByteArray();
- return this;
- }
-
- public MockResponse setChunkedBody(String body, int maxChunkSize) throws IOException {
- return setChunkedBody(body.getBytes(ASCII), maxChunkSize);
- }
-
- @Override public String toString() {
- return status;
- }
-
- public boolean shouldCloseConnectionAfter() {
- return closeConnectionAfter;
- }
-
- public MockResponse setCloseConnectionAfter(boolean closeConnectionAfter) {
- this.closeConnectionAfter = closeConnectionAfter;
- return this;
- }
-
- /**
- * Sets the header after which sending the server should close the connection.
- */
- public MockResponse setCloseConnectionAfterHeader(String header) {
- closeConnectionAfterHeader = header;
- setCloseConnectionAfter(true);
- return this;
- }
-
- /**
- * Returns the header after which sending the server should close the connection.
- */
- public String getCloseConnectionAfterHeader() {
- return closeConnectionAfterHeader;
- }
-
- /**
- * Sets the number of bytes in the body to send before which the server should close the
- * connection. Set to -1 to unset and send the entire body (default).
- */
- public MockResponse setCloseConnectionAfterXBytes(int position) {
- closeConnectionAfterXBytes = position;
- setCloseConnectionAfter(true);
- return this;
- }
-
- /**
- * Returns the number of bytes in the body to send before which the server should close the
- * connection. Returns -1 if the entire body should be sent (default).
- */
- public int getCloseConnectionAfterXBytes() {
- return closeConnectionAfterXBytes;
- }
-
- /**
- * Sets the number of bytes in the body to send before which the server should pause the
- * connection (stalls in sending data). Only one pause per response is supported.
- * Set to -1 to unset pausing (default).
- */
- public MockResponse setPauseConnectionAfterXBytes(int position) {
- pauseConnectionAfterXBytes = position;
- return this;
- }
-
- /**
- * Returns the number of bytes in the body to send before which the server should pause the
- * connection (stalls in sending data). (Returns -1 if it should not pause).
- */
- public int getPauseConnectionAfterXBytes() {
- return pauseConnectionAfterXBytes;
- }
-
- /**
- * Returns true if this response is flagged to pause the connection mid-stream, false otherwise
- */
- public boolean getShouldPause() {
- return (pauseConnectionAfterXBytes != -1);
- }
-
- /**
- * Returns true if this response is flagged to close the connection mid-stream, false otherwise
- */
- public boolean getShouldClose() {
- return (closeConnectionAfterXBytes != -1);
- }
-}
diff --git a/core/tests/utillib/src/coretestutils/http/MockWebServer.java b/core/tests/utillib/src/coretestutils/http/MockWebServer.java
deleted file mode 100644
index c329ffa..0000000
--- a/core/tests/utillib/src/coretestutils/http/MockWebServer.java
+++ /dev/null
@@ -1,426 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed 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 coretestutils.http;
-
-import java.io.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.net.MalformedURLException;
-import java.net.ServerSocket;
-import java.net.Socket;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Queue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.LinkedBlockingQueue;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-import android.util.Log;
-
-/**
- * A scriptable web server. Callers supply canned responses and the server
- * replays them upon request in sequence.
- *
- * TODO: merge with the version from libcore/support/src/tests/java once it's in.
- */
-public final class MockWebServer {
- static final String ASCII = "US-ASCII";
- static final String LOG_TAG = "coretestutils.http.MockWebServer";
-
- private final BlockingQueue<RecordedRequest> requestQueue
- = new LinkedBlockingQueue<RecordedRequest>();
- private final BlockingQueue<MockResponse> responseQueue
- = new LinkedBlockingQueue<MockResponse>();
- private int bodyLimit = Integer.MAX_VALUE;
- private final ExecutorService executor = Executors.newCachedThreadPool();
- // keep Futures around so we can rethrow any exceptions thrown by Callables
- private final Queue<Future<?>> futures = new LinkedList<Future<?>>();
- private final Object downloadPauseLock = new Object();
- // global flag to signal when downloads should resume on the server
- private volatile boolean downloadResume = false;
-
- private int port = -1;
-
- public int getPort() {
- if (port == -1) {
- throw new IllegalStateException("Cannot retrieve port before calling play()");
- }
- return port;
- }
-
- /**
- * Returns a URL for connecting to this server.
- *
- * @param path the request path, such as "/".
- */
- public URL getUrl(String path) throws MalformedURLException {
- return new URL("http://localhost:" + getPort() + path);
- }
-
- /**
- * Sets the number of bytes of the POST body to keep in memory to the given
- * limit.
- */
- public void setBodyLimit(int maxBodyLength) {
- this.bodyLimit = maxBodyLength;
- }
-
- public void enqueue(MockResponse response) {
- responseQueue.add(response);
- }
-
- /**
- * Awaits the next HTTP request, removes it, and returns it. Callers should
- * use this to verify the request sent was as intended.
- */
- public RecordedRequest takeRequest() throws InterruptedException {
- return requestQueue.take();
- }
-
- public RecordedRequest takeRequestWithTimeout(long timeoutMillis) throws InterruptedException {
- return requestQueue.poll(timeoutMillis, TimeUnit.MILLISECONDS);
- }
-
- public List<RecordedRequest> drainRequests() {
- List<RecordedRequest> requests = new ArrayList<RecordedRequest>();
- requestQueue.drainTo(requests);
- return requests;
- }
-
- /**
- * Starts the server, serves all enqueued requests, and shuts the server
- * down using the default (server-assigned) port.
- */
- public void play() throws IOException {
- play(0);
- }
-
- /**
- * Starts the server, serves all enqueued requests, and shuts the server
- * down.
- *
- * @param port The port number to use to listen to connections on; pass in 0 to have the
- * server automatically assign a free port
- */
- public void play(int portNumber) throws IOException {
- final ServerSocket ss = new ServerSocket(portNumber);
- ss.setReuseAddress(true);
- port = ss.getLocalPort();
- submitCallable(new Callable<Void>() {
- public Void call() throws Exception {
- int count = 0;
- while (true) {
- if (count > 0 && responseQueue.isEmpty()) {
- ss.close();
- executor.shutdown();
- return null;
- }
-
- serveConnection(ss.accept());
- count++;
- }
- }
- });
- }
-
- private void serveConnection(final Socket s) {
- submitCallable(new Callable<Void>() {
- public Void call() throws Exception {
- InputStream in = new BufferedInputStream(s.getInputStream());
- OutputStream out = new BufferedOutputStream(s.getOutputStream());
-
- int sequenceNumber = 0;
- while (true) {
- RecordedRequest request = readRequest(in, sequenceNumber);
- if (request == null) {
- if (sequenceNumber == 0) {
- throw new IllegalStateException("Connection without any request!");
- } else {
- break;
- }
- }
- requestQueue.add(request);
- MockResponse response = computeResponse(request);
- writeResponse(out, response);
- if (response.shouldCloseConnectionAfter()) {
- break;
- }
- sequenceNumber++;
- }
-
- in.close();
- out.close();
- return null;
- }
- });
- }
-
- private void submitCallable(Callable<?> callable) {
- Future<?> future = executor.submit(callable);
- futures.add(future);
- }
-
- /**
- * Check for and raise any exceptions that have been thrown by child threads. Will not block on
- * children still running.
- * @throws ExecutionException for the first child thread that threw an exception
- */
- public void checkForExceptions() throws ExecutionException, InterruptedException {
- final int originalSize = futures.size();
- for (int i = 0; i < originalSize; i++) {
- Future<?> future = futures.remove();
- try {
- future.get(0, TimeUnit.SECONDS);
- } catch (TimeoutException e) {
- futures.add(future); // still running
- }
- }
- }
-
- /**
- * @param sequenceNumber the index of this request on this connection.
- */
- private RecordedRequest readRequest(InputStream in, int sequenceNumber) throws IOException {
- String request = readAsciiUntilCrlf(in);
- if (request.equals("")) {
- return null; // end of data; no more requests
- }
-
- List<String> headers = new ArrayList<String>();
- int contentLength = -1;
- boolean chunked = false;
- String header;
- while (!(header = readAsciiUntilCrlf(in)).equals("")) {
- headers.add(header);
- String lowercaseHeader = header.toLowerCase();
- if (contentLength == -1 && lowercaseHeader.startsWith("content-length:")) {
- contentLength = Integer.parseInt(header.substring(15).trim());
- }
- if (lowercaseHeader.startsWith("transfer-encoding:") &&
- lowercaseHeader.substring(18).trim().equals("chunked")) {
- chunked = true;
- }
- }
-
- boolean hasBody = false;
- TruncatingOutputStream requestBody = new TruncatingOutputStream();
- List<Integer> chunkSizes = new ArrayList<Integer>();
- if (contentLength != -1) {
- hasBody = true;
- transfer(contentLength, in, requestBody);
- } else if (chunked) {
- hasBody = true;
- while (true) {
- int chunkSize = Integer.parseInt(readAsciiUntilCrlf(in).trim(), 16);
- if (chunkSize == 0) {
- readEmptyLine(in);
- break;
- }
- chunkSizes.add(chunkSize);
- transfer(chunkSize, in, requestBody);
- readEmptyLine(in);
- }
- }
-
- if (request.startsWith("GET ")) {
- if (hasBody) {
- throw new IllegalArgumentException("GET requests should not have a body!");
- }
- } else if (request.startsWith("POST ")) {
- if (!hasBody) {
- throw new IllegalArgumentException("POST requests must have a body!");
- }
- } else {
- throw new UnsupportedOperationException("Unexpected method: " + request);
- }
-
- return new RecordedRequest(request, headers, chunkSizes,
- requestBody.numBytesReceived, requestBody.toByteArray(), sequenceNumber);
- }
-
- /**
- * Returns a response to satisfy {@code request}.
- */
- private MockResponse computeResponse(RecordedRequest request) throws InterruptedException {
- if (responseQueue.isEmpty()) {
- throw new IllegalStateException("Unexpected request: " + request);
- }
- return responseQueue.take();
- }
-
- private void writeResponse(OutputStream out, MockResponse response) throws IOException {
- out.write((response.getStatus() + "\r\n").getBytes(ASCII));
- boolean doCloseConnectionAfterHeader = (response.getCloseConnectionAfterHeader() != null);
-
- // Send headers
- String closeConnectionAfterHeader = response.getCloseConnectionAfterHeader();
- for (String header : response.getHeaders()) {
- out.write((header + "\r\n").getBytes(ASCII));
-
- if (doCloseConnectionAfterHeader && header.startsWith(closeConnectionAfterHeader)) {
- Log.i(LOG_TAG, "Closing connection after header" + header);
- break;
- }
- }
-
- // Send actual body data
- if (!doCloseConnectionAfterHeader) {
- out.write(("\r\n").getBytes(ASCII));
-
- InputStream body = response.getBody();
- final int READ_BLOCK_SIZE = 10000; // process blocks this size
- byte[] currentBlock = new byte[READ_BLOCK_SIZE];
- int currentBlockSize = 0;
- int writtenSoFar = 0;
-
- boolean shouldPause = response.getShouldPause();
- boolean shouldClose = response.getShouldClose();
- int pause = response.getPauseConnectionAfterXBytes();
- int close = response.getCloseConnectionAfterXBytes();
-
- // Don't bother pausing if it's set to pause -after- the connection should be dropped
- if (shouldPause && shouldClose && (pause > close)) {
- shouldPause = false;
- }
-
- // Process each block we read in...
- while ((currentBlockSize = body.read(currentBlock)) != -1) {
- int startIndex = 0;
- int writeLength = currentBlockSize;
-
- // handle the case of pausing
- if (shouldPause && (writtenSoFar + currentBlockSize >= pause)) {
- writeLength = pause - writtenSoFar;
- out.write(currentBlock, 0, writeLength);
- out.flush();
- writtenSoFar += writeLength;
-
- // now pause...
- try {
- Log.i(LOG_TAG, "Pausing connection after " + pause + " bytes");
- // Wait until someone tells us to resume sending...
- synchronized(downloadPauseLock) {
- while (!downloadResume) {
- downloadPauseLock.wait();
- }
- // reset resume back to false
- downloadResume = false;
- }
- } catch (InterruptedException e) {
- Log.e(LOG_TAG, "Server was interrupted during pause in download.");
- }
-
- startIndex = writeLength;
- writeLength = currentBlockSize - writeLength;
- }
-
- // handle the case of closing the connection
- if (shouldClose && (writtenSoFar + writeLength > close)) {
- writeLength = close - writtenSoFar;
- out.write(currentBlock, startIndex, writeLength);
- writtenSoFar += writeLength;
- Log.i(LOG_TAG, "Closing connection after " + close + " bytes");
- break;
- }
- out.write(currentBlock, startIndex, writeLength);
- writtenSoFar += writeLength;
- }
- }
- out.flush();
- }
-
- /**
- * Transfer bytes from {@code in} to {@code out} until either {@code length}
- * bytes have been transferred or {@code in} is exhausted.
- */
- private void transfer(int length, InputStream in, OutputStream out) throws IOException {
- byte[] buffer = new byte[1024];
- while (length > 0) {
- int count = in.read(buffer, 0, Math.min(buffer.length, length));
- if (count == -1) {
- return;
- }
- out.write(buffer, 0, count);
- length -= count;
- }
- }
-
- /**
- * Returns the text from {@code in} until the next "\r\n", or null if
- * {@code in} is exhausted.
- */
- private String readAsciiUntilCrlf(InputStream in) throws IOException {
- StringBuilder builder = new StringBuilder();
- while (true) {
- int c = in.read();
- if (c == '\n' && builder.length() > 0 && builder.charAt(builder.length() - 1) == '\r') {
- builder.deleteCharAt(builder.length() - 1);
- return builder.toString();
- } else if (c == -1) {
- return builder.toString();
- } else {
- builder.append((char) c);
- }
- }
- }
-
- private void readEmptyLine(InputStream in) throws IOException {
- String line = readAsciiUntilCrlf(in);
- if (!line.equals("")) {
- throw new IllegalStateException("Expected empty but was: " + line);
- }
- }
-
- /**
- * An output stream that drops data after bodyLimit bytes.
- */
- private class TruncatingOutputStream extends ByteArrayOutputStream {
- private int numBytesReceived = 0;
- @Override public void write(byte[] buffer, int offset, int len) {
- numBytesReceived += len;
- super.write(buffer, offset, Math.min(len, bodyLimit - count));
- }
- @Override public void write(int oneByte) {
- numBytesReceived++;
- if (count < bodyLimit) {
- super.write(oneByte);
- }
- }
- }
-
- /**
- * Trigger the server to resume sending the download
- */
- public void doResumeDownload() {
- synchronized (downloadPauseLock) {
- downloadResume = true;
- downloadPauseLock.notifyAll();
- }
- }
-}
diff --git a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java b/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
deleted file mode 100644
index 293ff80..0000000
--- a/core/tests/utillib/src/coretestutils/http/RecordedRequest.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed 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 coretestutils.http;
-
-import java.util.List;
-
-/**
- * An HTTP request that came into the mock web server.
- */
-public final class RecordedRequest {
- private final String requestLine;
- private final List<String> headers;
- private final List<Integer> chunkSizes;
- private final int bodySize;
- private final byte[] body;
- private final int sequenceNumber;
-
- RecordedRequest(String requestLine, List<String> headers, List<Integer> chunkSizes,
- int bodySize, byte[] body, int sequenceNumber) {
- this.requestLine = requestLine;
- this.headers = headers;
- this.chunkSizes = chunkSizes;
- this.bodySize = bodySize;
- this.body = body;
- this.sequenceNumber = sequenceNumber;
- }
-
- public String getRequestLine() {
- return requestLine;
- }
-
- public List<String> getHeaders() {
- return headers;
- }
-
- /**
- * Returns the sizes of the chunks of this request's body, or an empty list
- * if the request's body was empty or unchunked.
- */
- public List<Integer> getChunkSizes() {
- return chunkSizes;
- }
-
- /**
- * Returns the total size of the body of this POST request (before
- * truncation).
- */
- public int getBodySize() {
- return bodySize;
- }
-
- /**
- * Returns the body of this POST request. This may be truncated.
- */
- public byte[] getBody() {
- return body;
- }
-
- /**
- * Returns the index of this request on its HTTP connection. Since a single
- * HTTP connection may serve multiple requests, each request is assigned its
- * own sequence number.
- */
- public int getSequenceNumber() {
- return sequenceNumber;
- }
-
- @Override public String toString() {
- return requestLine;
- }
-
- public String getMethod() {
- return getRequestLine().split(" ")[0];
- }
-
- public String getPath() {
- return getRequestLine().split(" ")[1];
- }
-}