httpHeaders) {
String raw = httpHeaders.get("cookie");
if (raw != null) {
String[] tokens = raw.split(";");
for (String token : tokens) {
String[] data = token.trim().split("=");
if (data.length == 2) {
this.cookies.put(data[0], data[1]);
}
}
}
}
/**
* Set a cookie with an expiration date from a month ago, effectively
* deleting it on the client side.
*
* @param name
* The cookie name.
*/
public void delete(String name) {
set(name, "-delete-", -30);
}
@Override
public Iterator iterator() {
return this.cookies.keySet().iterator();
}
/**
* Read a cookie from the HTTP Headers.
*
* @param name
* The cookie's name.
* @return The cookie's value if it exists, null otherwise.
*/
public String read(String name) {
return this.cookies.get(name);
}
public void set(Cookie cookie) {
this.queue.add(cookie);
//cookies.put(cookie.n, cookie.v); // CHANGED
}
/**
* Sets a cookie.
*
* @param name
* The cookie's name.
* @param value
* The cookie's value.
* @param expires
* How many days until the cookie expires.
*/
public Cookie set(String name, String value, int expires) {
return addAndReturn(this.queue, new Cookie(name, value, Cookie.getHTTPTime(expires)));
}
/**
* Internally used by the webserver to add all queued cookies into the
* Response's HTTP Headers.
*
* @param response
* The Response object to which headers the queued cookies
* will be added.
*/
public void unloadQueue(Response response) {
for (Cookie cookie : this.queue) {
response.addHeader("Set-Cookie", cookie.getHTTPHeader());
}
}
}
/**
* Default threading strategy for NanoHTTPD.
*
*
* By default, the server spawns a new Thread for every incoming request.
* These are set to daemon status, and named according to the request
* number. The name is useful when profiling the application.
*
*/
public static class DefaultAsyncRunner implements AsyncRunner {
private long requestCount;
private final List running = Collections.synchronizedList(new ArrayList());
/**
* @return a list with currently running clients.
*/
public List getRunning() {
return running;
}
@Override
public void closeAll() {
// copy of the list for concurrency
for (ClientHandler clientHandler : new ArrayList(this.running)) {
clientHandler.close();
}
}
@Override
public void closed(ClientHandler clientHandler) {
this.running.remove(clientHandler);
}
@Override
public void exec(ClientHandler clientHandler) {
++this.requestCount;
Thread t = new Thread(clientHandler);
//t.setDaemon(true);
String clientIP = "?";
try { clientIP = clientHandler.acceptSocket.getInetAddress().getHostAddress().toString(); } catch (Throwable __e) { _handleException(__e); }
t.setName("NanoHttpd serving request #" + this.requestCount + " to " + clientIP);
this.running.add(clientHandler);
t.start();
}
}
/**
* Default strategy for creating and cleaning up temporary files.
*
*
* By default, files are created by File.createTempFile()
in
* the directory specified.
*
*/
public static class DefaultTempFile implements TempFile {
private final File file;
private final OutputStream fstream;
public DefaultTempFile(String tempdir) throws IOException {
this.file = File.createTempFile("NanoHTTPD-", "", new File(tempdir));
this.fstream = new FileOutputStream(this.file);
System.err.println("Temp file created: " + file);
}
@Override
public void delete() throws Exception {
safeClose(this.fstream);
System.err.println("Temp file deleted: " + file);
if (!this.file.delete()) {
throw new Exception("could not delete temporary file");
}
}
@Override
public String getName() {
return this.file.getAbsolutePath();
}
@Override
public OutputStream open() throws Exception {
return this.fstream;
}
}
/**
* Default strategy for creating and cleaning up temporary files.
*
*
* This class stores its files in the standard location (that is, wherever
* java.io.tmpdir
points to). Files are added to an internal
* list, and deleted when no longer needed (that is, when
* clear()
is invoked at the end of processing a request).
*
*/
public static class DefaultTempFileManager implements TempFileManager {
private final String tmpdir;
private final List tempFiles;
public DefaultTempFileManager() {
this.tmpdir = tempDir().getPath(); // use JavaX, dude
// System.getProperty("java.io.tmpdir");
this.tempFiles = new ArrayList();
}
@Override
public void clear() {
for (TempFile file : this.tempFiles) {
try {
file.delete();
} catch (Exception ignored) {
NanoHTTPD.LOG.log(Level.WARNING, "could not delete file ", ignored);
}
}
this.tempFiles.clear();
}
@Override
public TempFile createTempFile() throws Exception {
DefaultTempFile tempFile = new DefaultTempFile(this.tmpdir);
this.tempFiles.add(tempFile);
return tempFile;
}
}
/**
* Default strategy for creating and cleaning up temporary files.
*/
private class DefaultTempFileManagerFactory implements TempFileManagerFactory {
@Override
public TempFileManager create() {
return new DefaultTempFileManager();
}
}
private static final String CONTENT_DISPOSITION_REGEX = "([ |\t]*Content-Disposition[ |\t]*:)(.*)";
private static final Pattern CONTENT_DISPOSITION_PATTERN = Pattern.compile(CONTENT_DISPOSITION_REGEX, Pattern.CASE_INSENSITIVE);
private static final String CONTENT_TYPE_REGEX = "([ |\t]*content-type[ |\t]*:)(.*)";
private static final Pattern CONTENT_TYPE_PATTERN = Pattern.compile(CONTENT_TYPE_REGEX, Pattern.CASE_INSENSITIVE);
private static final String CONTENT_DISPOSITION_ATTRIBUTE_REGEX = "[ |\t]*([a-zA-Z]*)[ |\t]*=[ |\t]*['|\"]([^\"^']*)['|\"]";
private static final Pattern CONTENT_DISPOSITION_ATTRIBUTE_PATTERN = Pattern.compile(CONTENT_DISPOSITION_ATTRIBUTE_REGEX);
class HTTPSession implements IHTTPSession {
boolean badClient = false;
long opened = sysNow();
public static final int BUFSIZE = 8192;
private final TempFileManager tempFileManager;
private final OutputStream outputStream;
private final PushbackInputStream inputStream;
private int splitbyte;
private int rlen;
private String uri;
private Method method;
private Map parms;
private Map headers;
Map files = new HashMap();
private CookieHandler cookies;
private String queryParameterString;
private String remoteIp;
private String protocolVersion;
public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream) {
this.tempFileManager = tempFileManager;
this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
this.outputStream = outputStream;
}
public HTTPSession(TempFileManager tempFileManager, InputStream inputStream, OutputStream outputStream, InetAddress inetAddress) {
this.tempFileManager = tempFileManager;
this.inputStream = new PushbackInputStream(inputStream, HTTPSession.BUFSIZE);
this.outputStream = outputStream;
this.remoteIp = inetAddress.isLoopbackAddress() || inetAddress.isAnyLocalAddress() ? "127.0.0.1" : inetAddress.getHostAddress().toString();
this.headers = new LinkedHashMap();
}
public void badClient(boolean b) { badClient = b; }
/**
* Decodes the sent headers and loads the data into Key/value pairs
*/
private void decodeHeader(BufferedReader in, Map pre, Map parms, Map headers, Map files) throws ResponseException {
try {
// Read the request line
String inLine = in.readLine();
if (inLine == null) {
return;
}
StringTokenizer st = new StringTokenizer(inLine);
if (!st.hasMoreTokens()) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error. Usage: GET /example/file.html");
}
pre.put("method", st.nextToken());
if (!st.hasMoreTokens()) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Missing URI. Usage: GET /example/file.html");
}
String uri = st.nextToken();
// Decode parameters from the URI
int qmi = uri.indexOf('?');
if (qmi >= 0) {
String query = uri.substring(qmi + 1);
files.put("query", query);
decodeParms(query, parms);
uri = uri.substring(0, qmi);
}
if (decodePercentInURI)
uri = decodePercent(uri);
// If there's another token, its protocol version,
// followed by HTTP headers.
// NOTE: this now forces header names lower case since they are
// case insensitive and vary by client.
if (st.hasMoreTokens()) {
protocolVersion = st.nextToken();
} else {
protocolVersion = "HTTP/1.1";
NanoHTTPD.LOG.log(Level.FINE, "no protocol version specified, strange. Assuming HTTP/1.1.");
}
String line = in.readLine();
while (line != null && line.trim().length() > 0) {
int p = line.indexOf(':');
if (p >= 0) {
headers.put(line.substring(0, p).trim().toLowerCase(Locale.US), line.substring(p + 1).trim());
}
line = in.readLine();
}
pre.put("uri", uri);
} catch (IOException ioe) {
throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage(), ioe);
}
}
/**
* Decodes the Multipart Body data and put it into Key/Value pairs.
*/
private void decodeMultipartFormData(String boundary, java.nio.ByteBuffer fbuf, Map parms, Map files) throws ResponseException {
try {
int[] boundary_idxs = getBoundaryPositions(fbuf, boundary.getBytes());
if (boundary_idxs.length < 2) {
throw new ResponseException(
Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but contains less than two boundary strings.");
}
final int MAX_HEADER_SIZE = 1024;
byte[] part_header_buff = new byte[MAX_HEADER_SIZE];
for (int bi = 0; bi < boundary_idxs.length - 1; bi++) {
fbuf.position(boundary_idxs[bi]);
int len = (fbuf.remaining() < MAX_HEADER_SIZE) ? fbuf.remaining() : MAX_HEADER_SIZE;
fbuf.get(part_header_buff, 0, len);
ByteArrayInputStream bais = new ByteArrayInputStream(part_header_buff, 0, len);
BufferedReader in = new BufferedReader(new InputStreamReader(bais, Charset.forName("US-ASCII")));
// First line is boundary string
String mpline = in.readLine();
if (!mpline.contains(boundary)) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Content type is multipart/form-data but chunk does not start with boundary.");
}
String part_name = null, file_name = null, content_type = null;
// Parse the reset of the header lines
mpline = in.readLine();
while (mpline != null && mpline.trim().length() > 0) {
Matcher matcher = CONTENT_DISPOSITION_PATTERN.matcher(mpline);
if (matcher.matches()) {
String attributeString = matcher.group(2);
matcher = CONTENT_DISPOSITION_ATTRIBUTE_PATTERN.matcher(attributeString);
while (matcher.find()) {
String key = matcher.group(1);
if (key.equalsIgnoreCase("name")) {
part_name = matcher.group(2);
} else if (key.equalsIgnoreCase("filename")) {
file_name = matcher.group(2);
}
}
}
matcher = CONTENT_TYPE_PATTERN.matcher(mpline);
if (matcher.matches()) {
content_type = matcher.group(2).trim();
}
mpline = in.readLine();
}
// Read the part data
int part_header_len = len - (int) in.skip(MAX_HEADER_SIZE);
if (part_header_len >= len - 4) {
throw new ResponseException(Status.INTERNAL_ERROR, "Multipart header size exceeds MAX_HEADER_SIZE.");
}
int part_data_start = boundary_idxs[bi] + part_header_len;
int part_data_end = boundary_idxs[bi + 1] - 4;
fbuf.position(part_data_start);
if (content_type == null) {
// Read the part into a string
byte[] data_bytes = new byte[part_data_end - part_data_start];
fbuf.get(data_bytes);
parms.put(part_name, new String(data_bytes));
} else {
// Read it into a file
String path = saveTmpFile(fbuf, part_data_start, part_data_end - part_data_start);
if (!files.containsKey(part_name)) {
files.put(part_name, path);
} else {
int count = 2;
while (files.containsKey(part_name + count)) {
count++;
}
files.put(part_name + count, path);
}
parms.put(part_name, file_name);
}
}
} catch (ResponseException re) {
throw re;
} catch (Exception e) {
throw new ResponseException(Status.INTERNAL_ERROR, e.toString());
}
}
/**
* Decodes parameters in percent-encoded URI-format ( e.g.
* "name=Jack%20Daniels&pass=Single%20Malt" ) and adds them to given
* Map. NOTE: this doesn't support multiple identical keys due to the
* simplicity of Map.
*/
private void decodeParms(String parms, Map p) {
if (parms == null) {
this.queryParameterString = "";
return;
}
this.queryParameterString = parms;
StringTokenizer st = new StringTokenizer(parms, "&");
while (st.hasMoreTokens()) {
String e = st.nextToken();
int sep = e.indexOf('=');
if (sep >= 0) {
p.put(decodePercent(e.substring(0, sep)).trim(), decodePercent(e.substring(sep + 1)));
} else {
p.put(decodePercent(e).trim(), "");
}
}
}
@Override
public void execute() throws IOException {
Response r = null;
try {
// Read the first 8192 bytes.
// The full header should fit in here.
// Apache's default header limit is 8KB.
// Do NOT assume that a single read will get the entire header
// at once!
byte[] buf = new byte[HTTPSession.BUFSIZE];
this.splitbyte = 0;
this.rlen = 0;
int read = -1;
try {
read = this.inputStream.read(buf, 0, HTTPSession.BUFSIZE);
} catch (Exception e) {
safeClose(this.inputStream);
safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
if (read == -1) {
// socket was been closed
safeClose(this.inputStream);
safeClose(this.outputStream);
throw new SocketException("NanoHttpd Shutdown");
}
while (read > 0) {
this.rlen += read;
this.splitbyte = findHeaderEnd(buf, this.rlen);
if (this.splitbyte > 0)
break;
if (rlen >= buf.length) {
if (NanoHTTPD_debug)
printWithIndent("HEADER> ", new String(buf));
throw fail("Header too big (" + rlen + " bytes)");
}
read = this.inputStream.read(buf, this.rlen, HTTPSession.BUFSIZE - this.rlen);
}
if (this.splitbyte < this.rlen) {
this.inputStream.unread(buf, this.splitbyte, this.rlen - this.splitbyte);
}
this.parms = new HashMap();
if (null == this.headers) {
this.headers = new HashMap();
} else {
this.headers.clear();
}
if (null != this.remoteIp) {
this.headers.put("remote-addr", this.remoteIp);
this.headers.put("http-client-ip", this.remoteIp);
}
// Create a BufferedReader for parsing the header.
BufferedReader hin = new BufferedReader(new InputStreamReader(new ByteArrayInputStream(buf, 0, this.rlen)));
// Decode the header into parms and header java properties
Map pre = new LinkedHashMap();
decodeHeader(hin, pre, this.parms, this.headers, this.files);
this.method = Method.lookup(pre.get("method"));
if (this.method == null) {
throw new ResponseException(Status.BAD_REQUEST, "BAD REQUEST: Syntax error.");
}
this.uri = pre.get("uri");
this.cookies = new CookieHandler(this.headers);
String connection = this.headers.get("connection");
boolean keepAlive = protocolVersion.equals("HTTP/1.1") && (connection == null || !connection.matches("(?i).*close.*"));
// Ok, now do the serve()
r = serve(this);
if (badClient) return;
if (r == null) {
throw new ResponseException(Status.INTERNAL_ERROR, "SERVER INTERNAL ERROR: Serve() returned a null response.");
} else {
String acceptEncoding = this.headers.get("accept-encoding");
this.cookies.unloadQueue(r);
r.setRequestMethod(this.method);
r.setGzipEncoding(useGzipWhenAccepted(r) && acceptEncoding != null && acceptEncoding.contains("gzip"));
r.setKeepAlive(keepAlive);
r.send(this.outputStream);
}
if (!keepAlive || "close".equalsIgnoreCase(r.getHeader("connection"))) {
throw new SocketException("NanoHttpd Shutdown");
}
} catch (SocketException e) {
// throw it out to close socket object (finalAccept)
throw e;
} catch (SocketTimeoutException ste) {
// treat socket timeouts the same way we treat socket exceptions
// i.e. close the stream & finalAccept object by throwing the
// exception up the call stack.
throw ste;
} catch (IOException ioe) {
Response resp = newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
resp.send(this.outputStream);
safeClose(this.outputStream);
} catch (ResponseException re) {
Response resp = newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
resp.send(this.outputStream);
safeClose(this.outputStream);
} finally {
if (badClient)
badClients.add(this);
else
safeClose(r);
this.tempFileManager.clear();
}
}
/**
* Find byte index separating header from body. It must be the last byte
* of the first two sequential new lines.
*/
private int findHeaderEnd(final byte[] buf, int rlen) {
int splitbyte = 0;
while (splitbyte + 3 < rlen) {
if (buf[splitbyte] == '\r' && buf[splitbyte + 1] == '\n' && buf[splitbyte + 2] == '\r' && buf[splitbyte + 3] == '\n') {
return splitbyte + 4;
}
splitbyte++;
}
return 0;
}
/**
* Find the byte positions where multipart boundaries start. This reads
* a large block at a time and uses a temporary buffer to optimize
* (memory mapped) file access.
*/
private int[] getBoundaryPositions(java.nio.ByteBuffer b, byte[] boundary) {
int[] res = new int[0];
if (b.remaining() < boundary.length) {
return res;
}
int search_window_pos = 0;
byte[] search_window = new byte[4 * 1024 + boundary.length];
int first_fill = (b.remaining() < search_window.length) ? b.remaining() : search_window.length;
b.get(search_window, 0, first_fill);
int new_bytes = first_fill - boundary.length;
do {
// Search the search_window
for (int j = 0; j < new_bytes; j++) {
for (int i = 0; i < boundary.length; i++) {
if (search_window[j + i] != boundary[i])
break;
if (i == boundary.length - 1) {
// Match found, add it to results
int[] new_res = new int[res.length + 1];
System.arraycopy(res, 0, new_res, 0, res.length);
new_res[res.length] = search_window_pos + j;
res = new_res;
}
}
}
search_window_pos += new_bytes;
// Copy the end of the buffer to the start
System.arraycopy(search_window, search_window.length - boundary.length, search_window, 0, boundary.length);
// Refill search_window
new_bytes = search_window.length - boundary.length;
new_bytes = (b.remaining() < new_bytes) ? b.remaining() : new_bytes;
b.get(search_window, boundary.length, new_bytes);
} while (new_bytes > 0);
return res;
}
@Override
public CookieHandler getCookies() {
return this.cookies;
}
@Override
public final Map getHeaders() {
return this.headers;
}
@Override
public final Map getFiles() {
return this.files;
}
@Override
public final InputStream getInputStream() {
return this.inputStream;
}
@Override
public final Method getMethod() {
return this.method;
}
@Override
public final Map getParms() {
return this.parms;
}
@Override
public String getQueryParameterString() {
return this.queryParameterString;
}
private RandomAccessFile getTmpBucket() {
try {
TempFile tempFile = this.tempFileManager.createTempFile();
return new RandomAccessFile(tempFile.getName(), "rw");
} catch (Exception e) {
throw new Error(e); // we won't recover, so throw an error
}
}
@Override
public final String getUri() {
return this.uri;
}
@Override
public void parseBody() throws IOException, ResponseException {
final int REQUEST_BUFFER_LEN = 512;
final int MEMORY_STORE_LIMIT = 1024;
RandomAccessFile randomAccessFile = null;
try {
long size;
if (this.headers.containsKey("content-length")) {
size = Integer.parseInt(this.headers.get("content-length"));
} else if (this.splitbyte < this.rlen) {
size = this.rlen - this.splitbyte;
} else {
size = 0;
}
ByteArrayOutputStream baos = null;
DataOutput request_data_output = null;
// Store the request in memory or a file, depending on size
if (size < MEMORY_STORE_LIMIT) {
baos = new ByteArrayOutputStream();
request_data_output = new DataOutputStream(baos);
} else {
randomAccessFile = getTmpBucket();
request_data_output = randomAccessFile;
}
// Read all the body and write it to request_data_output
byte[] buf = new byte[REQUEST_BUFFER_LEN];
while (this.rlen >= 0 && size > 0) {
this.rlen = this.inputStream.read(buf, 0, (int) Math.min(size, REQUEST_BUFFER_LEN));
size -= this.rlen;
if (this.rlen > 0) {
request_data_output.write(buf, 0, this.rlen);
}
}
java.nio.ByteBuffer fbuf = null;
if (baos != null) {
fbuf = java.nio.ByteBuffer.wrap(baos.toByteArray(), 0, baos.size());
} else {
fbuf = randomAccessFile.getChannel().map(FileChannel.MapMode.READ_ONLY, 0, randomAccessFile.length());
randomAccessFile.seek(0);
}
// If the method is POST, there may be parameters
// in data section, too, read it:
if (Method.POST.equals(this.method)) {
String contentType = "";
String contentTypeHeader = this.headers.get("content-type");
StringTokenizer st = null;
if (contentTypeHeader != null) {
st = new StringTokenizer(contentTypeHeader, ",; ");
if (st.hasMoreTokens()) {
contentType = st.nextToken();
}
}
if ("multipart/form-data".equalsIgnoreCase(contentType)) {
// Handle multipart/form-data
if (!st.hasMoreTokens()) {
throw new ResponseException(Status.BAD_REQUEST,
"BAD REQUEST: Content type is multipart/form-data but boundary missing. Usage: GET /example/file.html");
}
String boundaryStartString = "boundary=";
int boundaryContentStart = contentTypeHeader.indexOf(boundaryStartString) + boundaryStartString.length();
String boundary = contentTypeHeader.substring(boundaryContentStart, contentTypeHeader.length());
if (boundary.startsWith("\"") && boundary.endsWith("\"")) {
boundary = boundary.substring(1, boundary.length() - 1);
}
decodeMultipartFormData(boundary, fbuf, this.parms, files);
} else {
byte[] postBytes = new byte[fbuf.remaining()];
if (NanoHTTPD_debug)
print("NanoHTTPD: Handling POST data (" + l(postBytes) + " bytes)");
fbuf.get(postBytes);
String postLine = new String(postBytes).trim();
// Handle application/x-www-form-urlencoded
if ("application/x-www-form-urlencoded".equalsIgnoreCase(contentType)) {
decodeParms(postLine, this.parms);
} else if (postLine.length() != 0) {
// Special case for raw POST data => create a
// special files entry "postData" with raw content
// data
files.put("postData", postLine);
}
}
} else if (Method.PUT.equals(this.method)) {
files.put("content", saveTmpFile(fbuf, 0, fbuf.limit()));
}
} finally {
safeClose(randomAccessFile);
}
}
/**
* Retrieves the content of a sent file and saves it to a temporary
* file. The full path to the saved file is returned.
*/
private String saveTmpFile(java.nio.ByteBuffer b, int offset, int len) {
String path = "";
if (len > 0) {
FileOutputStream fileOutputStream = null;
try {
TempFile tempFile = this.tempFileManager.createTempFile();
java.nio.ByteBuffer src = b.duplicate();
fileOutputStream = new FileOutputStream(tempFile.getName());
FileChannel dest = fileOutputStream.getChannel();
src.position(offset).limit(offset + len);
dest.write(src.slice());
path = tempFile.getName();
} catch (Exception e) { // Catch exception if any
throw new Error(e); // we won't recover, so throw an error
} finally {
safeClose(fileOutputStream);
}
}
return path;
}
public String remoteIp() { return remoteIp; }
} // end of HTTPSession
/**
* Handles one session, i.e. parses the HTTP request and returns the
* response.
*/
public interface IHTTPSession {
void badClient(boolean b);
void execute() throws IOException;
CookieHandler getCookies();
Map getHeaders();
Map getFiles();
InputStream getInputStream();
Method getMethod();
Map getParms();
String getQueryParameterString();
/**
* @return the path part of the URL.
*/
String getUri();
/**
* Adds the files in the request body to the files map.
*
* @param files
* map to modify
*/
void parseBody() throws IOException, ResponseException;
String remoteIp();
}
/**
* HTTP Request methods, with the ability to decode a String
* back to its enum value.
*/
public enum Method {
GET,
PUT,
POST,
DELETE,
HEAD,
OPTIONS,
TRACE,
CONNECT,
PATCH;
static Method lookup(String method) {
for (Method m : Method.values()) {
if (m.toString().equalsIgnoreCase(method)) {
return m;
}
}
return null;
}
}
/**
* HTTP response. Return one of these from serve().
*/
public static class Response implements Closeable {
public String toString() {
return "HttpResponse " + status + " " + mimeType;
}
/**
* Output stream that will automatically send every write to the wrapped
* OutputStream according to chunked transfer:
* http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.6.1
*/
private static class ChunkedOutputStream extends FilterOutputStream {
public ChunkedOutputStream(OutputStream out) {
super(out);
}
@Override
public void write(int b) throws IOException {
byte[] data = {
(byte) b
};
write(data, 0, 1);
}
@Override
public void write(byte[] b) throws IOException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
if (len == 0)
return;
out.write(String.format("%x\r\n", len).getBytes());
out.write(b, off, len);
out.write("\r\n".getBytes());
}
public void finish() throws IOException {
out.write("0\r\n\r\n".getBytes());
}
}
/**
* HTTP status code after processing, e.g. "200 OK", Status.OK
*/
private IStatus status;
/**
* MIME type of content, e.g. "text/html"
*/
private String mimeType;
/**
* Data of the response, may be null.
*/
private InputStream data;
private long contentLength;
/**
* Headers for the HTTP response. Use addHeader() to add lines.
*/
//private final Map header = new HashMap();
final Map header = lithashmap("X-Powered-By", "JavaX");
/**
* The request method that spawned this response.
*/
private Method requestMethod;
/**
* Use chunkedTransfer
*/
private boolean chunkedTransfer = false;
private boolean encodeAsGzip = false;
private boolean keepAlive = false;
/**
* Creates a fixed length response if totalBytes>=0, otherwise chunked.
*/
protected Response(IStatus status, String mimeType, InputStream data, long totalBytes) {
this.status = status;
this.mimeType = mimeType;
if (data == null) {
this.data = new ByteArrayInputStream(new byte[0]);
this.contentLength = 0L;
} else {
this.data = data;
this.contentLength = totalBytes;
}
this.chunkedTransfer = this.contentLength < 0;
keepAlive = true;
}
@Override
public void close() throws IOException {
if (this.data != null) {
this.data.close();
}
}
/**
* Adds given line to the header.
*/
public void addHeader(String name, String value) {
this.header.put(name, value);
}
public InputStream getData() {
return this.data;
}
public String getHeader(String name) {
for (String headerName : header.keySet()) {
if (headerName.equalsIgnoreCase(name)) {
return header.get(headerName);
}
}
return null;
}
public String getMimeType() {
return this.mimeType;
}
public Method getRequestMethod() {
return this.requestMethod;
}
public IStatus getStatus() {
return this.status;
}
public void setGzipEncoding(boolean encodeAsGzip) {
this.encodeAsGzip = encodeAsGzip;
}
public void setKeepAlive(boolean useKeepAlive) {
this.keepAlive = useKeepAlive;
}
private boolean headerAlreadySent(Map header, String name) {
boolean alreadySent = false;
for (String headerName : header.keySet()) {
alreadySent |= headerName.equalsIgnoreCase(name);
}
return alreadySent;
}
/**
* Sends given response to the socket.
*/
protected void send(OutputStream outputStream) {
String mime = this.mimeType;
SimpleDateFormat gmtFrmt = new SimpleDateFormat("E, d MMM yyyy HH:mm:ss 'GMT'", Locale.US);
gmtFrmt.setTimeZone(TimeZone.getTimeZone("GMT"));
try {
if (this.status == null) {
throw new Error("sendResponse(): Status can't be null.");
}
PrintWriter pw = new PrintWriter(new BufferedWriter(new OutputStreamWriter(outputStream, "UTF-8")), false);
pw.print("HTTP/1.1 " + this.status.getDescription() + " \r\n");
if (mime != null) {
pw.print("Content-Type: " + mime + "\r\n");
}
if (this.header == null || this.header.get("Date") == null) {
pw.print("Date: " + gmtFrmt.format(new Date()) + "\r\n");
}
if (this.header != null) {
for (String key : this.header.keySet()) {
String value = this.header.get(key);
pw.print(key + ": " + value + "\r\n");
}
}
if (!headerAlreadySent(header, "connection")) {
pw.print("Connection: " + (this.keepAlive ? "keep-alive" : "close") + "\r\n");
}
if (headerAlreadySent(this.header, "content-length")) {
encodeAsGzip = false;
}
if (encodeAsGzip) {
pw.print("Content-Encoding: gzip\r\n");
setChunkedTransfer(true);
}
long pending = this.data != null ? this.contentLength : 0;
if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
pw.print("Transfer-Encoding: chunked\r\n");
} else if (!encodeAsGzip) {
pending = sendContentLengthHeaderIfNotAlreadyPresent(pw, this.header, pending);
}
pw.print("\r\n");
pw.flush();
sendBodyWithCorrectTransferAndEncoding(outputStream, pending);
outputStream.flush();
safeClose(this.data);
} catch (IOException ioe) {
NanoHTTPD.LOG.log(Level.SEVERE, "Could not send response to the client", ioe);
}
}
private void sendBodyWithCorrectTransferAndEncoding(OutputStream outputStream, long pending) throws IOException {
if (this.requestMethod != Method.HEAD && this.chunkedTransfer) {
ChunkedOutputStream chunkedOutputStream = new ChunkedOutputStream(outputStream);
sendBodyWithCorrectEncoding(chunkedOutputStream, -1);
chunkedOutputStream.finish();
} else {
sendBodyWithCorrectEncoding(outputStream, pending);
}
}
private void sendBodyWithCorrectEncoding(OutputStream outputStream, long pending) throws IOException {
if (encodeAsGzip) {
GZIPOutputStream gzipOutputStream = new GZIPOutputStream(outputStream);
sendBody(gzipOutputStream, -1);
gzipOutputStream.finish();
} else {
sendBody(outputStream, pending);
}
}
/**
* Sends the body to the specified OutputStream. The pending parameter
* limits the maximum amounts of bytes sent unless it is -1, in which
* case everything is sent.
*
* @param outputStream
* the OutputStream to send data to
* @param pending
* -1 to send everything, otherwise sets a max limit to the
* number of bytes sent
* @throws IOException
* if something goes wrong while sending the data.
*/
private void sendBody(OutputStream outputStream, long pending) throws IOException {
long BUFFER_SIZE = 16 * 1024;
byte[] buff = new byte[(int) BUFFER_SIZE];
boolean sendEverything = pending == -1;
while (pending > 0 || sendEverything) {
long bytesToRead = sendEverything ? BUFFER_SIZE : Math.min(pending, BUFFER_SIZE);
int read = this.data.read(buff, 0, (int) bytesToRead);
if (read <= 0) {
break;
}
outputStream.write(buff, 0, read);
if (!sendEverything) {
pending -= read;
}
}
}
protected long sendContentLengthHeaderIfNotAlreadyPresent(PrintWriter pw, Map header, long size) {
for (String headerName : header.keySet()) {
if (headerName.equalsIgnoreCase("content-length")) {
try {
return Long.parseLong(header.get(headerName));
} catch (NumberFormatException ex) {
return size;
}
}
}
pw.print("Content-Length: " + size + "\r\n");
return size;
}
public void setChunkedTransfer(boolean chunkedTransfer) {
this.chunkedTransfer = chunkedTransfer;
}
public void setData(InputStream data) {
this.data = data;
}
public void setMimeType(String mimeType) {
this.mimeType = mimeType;
}
public void setRequestMethod(Method requestMethod) {
this.requestMethod = requestMethod;
}
public void setStatus(IStatus status) {
this.status = status;
}
}
public static final class ResponseException extends Exception {
private static final long serialVersionUID = 6569838532917408380L;
private final Status status;
public ResponseException(Status status, String message) {
super(message);
this.status = status;
}
public ResponseException(Status status, String message, Exception e) {
super(message, e);
this.status = status;
}
public Status getStatus() {
return this.status;
}
}
/**
* The runnable that will be used for the main listening thread.
*/
public class ServerRunnable implements Runnable {
private final int timeout;
private IOException bindException;
private boolean hasBinded = false;
private ServerRunnable(int timeout) {
this.timeout = timeout;
}
@Override
public void run() {
try {
myServerSocket.bind(hostname != null ? new InetSocketAddress(hostname, myPort) : new InetSocketAddress(myPort));
hasBinded = true;
} catch (IOException e) {
print("Was trying to bind to port: " + myPort + (hostname == null ? "" : " on " + hostname));
this.bindException = e;
return;
}
do {
try {
final Socket finalAccept = NanoHTTPD.this.myServerSocket.accept();
if (NanoHTTPD_debug) print("NanoHTTPD: New socket.");
if (this.timeout > 0) {
finalAccept.setSoTimeout(this.timeout);
}
final InputStream inputStream = wrapStuff("SocketInputStream", finalAccept.getInputStream(), finalAccept, NanoHTTPD.this.myServerSocket);
if (NanoHTTPD_debug) print("NanoHTTPD: Have input stream.");
NanoHTTPD.this.asyncRunner.exec(createClientHandler(finalAccept, inputStream));
} catch (IOException e) {
NanoHTTPD.LOG.log(Level.FINE, "Communication with the client broken", e);
}
} while (!NanoHTTPD.this.myServerSocket.isClosed());
}
}
/**
* A temp file.
*
*
* Temp files are responsible for managing the actual temporary storage and
* cleaning themselves up when no longer needed.
*
*/
public interface TempFile {
void delete() throws Exception;
String getName();
OutputStream open() throws Exception;
}
/**
* Temp file manager.
*
*
* Temp file managers are created 1-to-1 with incoming requests, to create
* and cleanup temporary files created as a result of handling the request.
*
*/
public interface TempFileManager {
void clear();
TempFile createTempFile() throws Exception;
}
/**
* Factory to create temp file managers.
*/
public interface TempFileManagerFactory {
TempFileManager create();
}
/**
* Maximum time to wait on Socket.getInputStream().read() (in milliseconds)
* This is required as the Keep-Alive HTTP connections would otherwise block
* the socket reading thread forever (or as long the browser is open).
*/
public static int SOCKET_READ_TIMEOUT =
// 5000;
24*3600*1000; // for WebSockets!
/**
* Common MIME type for dynamic content: plain text
*/
public static final String MIME_PLAINTEXT = "text/plain; charset=utf-8";
/**
* Common MIME type for dynamic content: html
*/
public static final String MIME_HTML = "text/html; charset=utf-8";
/**
* Pseudo-Parameter to use to store the actual query string in the
* parameters map for later re-processing.
*/
private static final String QUERY_STRING_PARAMETER = "NanoHttpd.QUERY_STRING";
/**
* logger to log to.
*/
private static final Logger LOG = Logger.getLogger(NanoHTTPD.class.getName());
/**
* Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and an
* array of loaded KeyManagers. These objects must properly
* loaded/initialized by the caller.
*/
public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManager[] keyManagers) throws IOException {
SSLServerSocketFactory res = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadedKeyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(keyManagers, trustManagerFactory.getTrustManagers(), null);
res = ctx.getServerSocketFactory();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
return res;
}
/**
* Creates an SSLSocketFactory for HTTPS. Pass a loaded KeyStore and a
* loaded KeyManagerFactory. These objects must properly loaded/initialized
* by the caller.
*/
public static SSLServerSocketFactory makeSSLSocketFactory(KeyStore loadedKeyStore, KeyManagerFactory loadedKeyFactory) throws IOException {
SSLServerSocketFactory res = null;
try {
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(loadedKeyStore);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(loadedKeyFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
res = ctx.getServerSocketFactory();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
return res;
}
/**
* Creates an SSLSocketFactory for HTTPS. Pass a KeyStore resource with your
* certificate and passphrase
*/
public static SSLServerSocketFactory makeSSLSocketFactory(String keyAndTrustStoreClasspathPath, char[] passphrase) throws IOException {
SSLServerSocketFactory res = null;
try {
KeyStore keystore = KeyStore.getInstance(KeyStore.getDefaultType());
InputStream keystoreStream = NanoHTTPD.class.getResourceAsStream(keyAndTrustStoreClasspathPath);
keystore.load(keystoreStream, passphrase);
TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(keystore);
KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(keystore, passphrase);
SSLContext ctx = SSLContext.getInstance("TLS");
ctx.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), null);
res = ctx.getServerSocketFactory();
} catch (Exception e) {
throw new IOException(e.getMessage());
}
return res;
}
private static final void safeClose(Object closeable) {
try {
if (closeable != null) {
if (closeable instanceof Closeable) {
((Closeable) closeable).close();
} else if (closeable instanceof Socket) {
((Socket) closeable).close();
} else if (closeable instanceof ServerSocket) {
((ServerSocket) closeable).close();
} else {
throw new IllegalArgumentException("Unknown object to close");
}
}
} catch (IOException e) {
NanoHTTPD.LOG.log(Level.SEVERE, "Could not close", e);
}
}
private final String hostname;
private final int myPort;
private ServerSocket myServerSocket;
private SSLServerSocketFactory sslServerSocketFactory;
private Thread myThread;
/**
* Pluggable strategy for asynchronously executing requests.
*/
protected AsyncRunner asyncRunner;
/**
* Pluggable strategy for creating and cleaning up temporary files.
*/
private TempFileManagerFactory tempFileManagerFactory;
/**
* Constructs an HTTP server on given port.
*/
public NanoHTTPD(int port) {
this(null, port);
}
// -------------------------------------------------------------------------------
// //
//
// Threading Strategy.
//
// -------------------------------------------------------------------------------
// //
/**
* Constructs an HTTP server on given hostname and port.
*/
public NanoHTTPD(String hostname, int port) {
this.hostname = hostname;
this.myPort = port;
setTempFileManagerFactory(new DefaultTempFileManagerFactory());
setAsyncRunner(new DefaultAsyncRunner());
}
/**
* Forcibly closes all connections that are open.
*/
public synchronized void closeAllConnections() {
stop();
}
/**
* create a instance of the client handler, subclasses can return a subclass
* of the ClientHandler.
*
* @param finalAccept
* the socket the cleint is connected to
* @param inputStream
* the input stream
* @return the client handler
*/
protected ClientHandler createClientHandler(final Socket finalAccept, final InputStream inputStream) {
return new ClientHandler(inputStream, finalAccept);
}
/**
* Instantiate the server runnable, can be overwritten by subclasses to
* provide a subclass of the ServerRunnable.
*
* @param timeout
* the socet timeout to use.
* @return the server runnable.
*/
protected ServerRunnable createServerRunnable(final int timeout) {
return new ServerRunnable(timeout);
}
/**
* Decode parameters from a URL, handing the case where a single parameter
* name might have been supplied several times, by return lists of values.
* In general these lists will contain a single element.
*
* @param parms
* original NanoHTTPD parameters values, as passed to the
* serve()
method.
* @return a map of String
(parameter name) to
* List<String>
(a list of the values supplied).
*/
protected Map> decodeParameters(Map parms) {
return this.decodeParameters(parms.get(NanoHTTPD.QUERY_STRING_PARAMETER));
}
// -------------------------------------------------------------------------------
// //
/**
* Decode parameters from a URL, handing the case where a single parameter
* name might have been supplied several times, by return lists of values.
* In general these lists will contain a single element.
*
* @param queryString
* a query string pulled from the URL.
* @return a map of String
(parameter name) to
* List<String>
(a list of the values supplied).
*/
protected Map> decodeParameters(String queryString) {
Map> parms = new HashMap>();
if (queryString != null) {
StringTokenizer st = new StringTokenizer(queryString, "&");
while (st.hasMoreTokens()) {
String e = st.nextToken();
int sep = e.indexOf('=');
String propertyName = sep >= 0 ? decodePercent(e.substring(0, sep)).trim() : decodePercent(e).trim();
if (!parms.containsKey(propertyName)) {
parms.put(propertyName, new ArrayList());
}
String propertyValue = sep >= 0 ? decodePercent(e.substring(sep + 1)) : null;
// XXX Stefan - allow raw parameters - took the if out:
/* if (propertyValue != null) */ {
parms.get(propertyName).add(propertyValue);
}
}
}
return parms;
}
/**
* Decode percent encoded String
values.
*
* @param str
* the percent encoded String
* @return expanded form of the input, for example "foo%20bar" becomes
* "foo bar"
*/
protected String decodePercent(String str) {
String decoded = null;
try {
decoded = URLDecoder.decode(str, "UTF8");
} catch (UnsupportedEncodingException ignored) {
NanoHTTPD.LOG.log(Level.WARNING, "Encoding not supported, ignored", ignored);
}
return decoded;
}
/**
* @return true if the gzip compression should be used if the client
* accespts it. Default this option is on for text content and off
* for everything else.
*/
protected boolean useGzipWhenAccepted(Response r) {
return r.getMimeType() != null && r.getMimeType().toLowerCase().contains("text/");
}
public final int getListeningPort() {
return this.myServerSocket == null ? -1 : this.myServerSocket.getLocalPort();
}
public final boolean isAlive() {
return wasStarted() && !this.myServerSocket.isClosed() && this.myThread.isAlive();
}
public void join() throws InterruptedException {
myThread.join();
}
/**
* Call before start() to serve over HTTPS instead of HTTP
*/
public void makeSecure(SSLServerSocketFactory sslServerSocketFactory) {
this.sslServerSocketFactory = sslServerSocketFactory;
}
/**
* Create a response with unknown length (using HTTP 1.1 chunking).
*/
public static Response newChunkedResponse(IStatus status, String mimeType, InputStream data) {
return new Response(status, mimeType, data, -1);
}
/**
* Create a response with known length.
*/
public static Response newFixedLengthResponse(IStatus status, String mimeType, InputStream data, long totalBytes) {
return new Response(status, mimeType, data, totalBytes);
}
/**
* Create a text response with known length.
*/
public static Response newFixedLengthResponse(IStatus status, String mimeType, String txt) {
if (txt == null) {
return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(new byte[0]), 0);
} else {
byte[] bytes;
try {
bytes = txt.getBytes("UTF-8");
} catch (UnsupportedEncodingException e) {
NanoHTTPD.LOG.log(Level.SEVERE, "encoding problem, responding nothing", e);
bytes = new byte[0];
}
return newFixedLengthResponse(status, mimeType, new ByteArrayInputStream(bytes), bytes.length);
}
}
/**
* Create a text response with known length.
*/
public static Response newFixedLengthResponse(String msg) {
return newFixedLengthResponse(Status.OK, NanoHTTPD.MIME_HTML, msg);
}
/**
* Override this to customize the server.
*
*
* (By default, this returns a 404 "Not Found" plain text error response.)
*
* @param session
* The HTTP session
* @return HTTP response, see class Response for details
*/
public Response serve(IHTTPSession session) {
currentSession.set(session);
Method method = session.getMethod();
if (Method.PUT.equals(method) || Method.POST.equals(method)) {
try {
session.parseBody();
} catch (IOException ioe) {
return newFixedLengthResponse(Status.INTERNAL_ERROR, NanoHTTPD.MIME_PLAINTEXT, "SERVER INTERNAL ERROR: IOException: " + ioe.getMessage());
} catch (ResponseException re) {
return newFixedLengthResponse(re.getStatus(), NanoHTTPD.MIME_PLAINTEXT, re.getMessage());
}
}
Map parms = session.getParms();
if (!noQueryStringParameter)
parms.put(NanoHTTPD.QUERY_STRING_PARAMETER, session.getQueryParameterString());
return serve(session.getUri(), method, session.getHeaders(), parms, session.getFiles());
}
/**
* Override this to customize the server.
*
*
* (By default, this returns a 404 "Not Found" plain text error response.)
*
* @param uri
* Percent-decoded URI without parameters, for example
* "/index.cgi"
* @param method
* "GET", "POST" etc.
* @param parms
* Parsed, percent decoded parameters from URI and, in case of
* POST, data.
* @param headers
* Header entries, percent decoded
* @return HTTP response, see class Response for details
*/
@Deprecated
public Response serve(String uri, Method method, Map headers, Map parms, Map files) {
return newFixedLengthResponse(Status.NOT_FOUND, NanoHTTPD.MIME_PLAINTEXT, "Not Found");
}
/**
* Pluggable strategy for asynchronously executing requests.
*
* @param asyncRunner
* new strategy for handling threads.
*/
public void setAsyncRunner(AsyncRunner asyncRunner) {
this.asyncRunner = asyncRunner;
}
/**
* Pluggable strategy for creating and cleaning up temporary files.
*
* @param tempFileManagerFactory
* new strategy for handling temp files.
*/
public void setTempFileManagerFactory(TempFileManagerFactory tempFileManagerFactory) {
this.tempFileManagerFactory = tempFileManagerFactory;
}
/**
* Start the server.
*
* @throws IOException
* if the socket is in use.
*/
public void start() throws IOException {
start(NanoHTTPD.SOCKET_READ_TIMEOUT);
}
/**
* Start the server.
*
* @param timeout
* timeout to use for socket connections.
* @throws IOException
* if the socket is in use.
*/
public void start(final int timeout) throws IOException {
boolean ssl = this.sslServerSocketFactory != null;
if (ssl) {
SSLServerSocket ss = (SSLServerSocket) this.sslServerSocketFactory.createServerSocket();
ss.setNeedClientAuth(false);
this.myServerSocket = ss;
} else {
this.myServerSocket = new ServerSocket();
}
this.myServerSocket.setReuseAddress(true);
ServerRunnable serverRunnable = createServerRunnable(timeout);
this.myThread = new Thread(serverRunnable);
//this.myThread.setDaemon(true);
this.myThread.setName("NanoHttpd Main Listener");
this.myThread.start();
while (!serverRunnable.hasBinded && serverRunnable.bindException == null) {
try {
Thread.sleep(10L);
} catch (Throwable e) {
// on android this may not be allowed, that's why we
// catch throwable the wait should be very short because we are
// just waiting for the bind of the socket
}
}
if (serverRunnable.bindException != null) {
throw serverRunnable.bindException;
}
System.out.println("HTTP" + (ssl ? "S" : "") + " server started (listening on port " + getListeningPort() + "!)");
printMyIPs();
}
/**
* Stop the server.
*/
public void stop() {
try {
safeClose(this.myServerSocket);
this.asyncRunner.closeAll();
if (this.myThread != null) {
this.myThread.join();
}
} catch (Exception e) {
NanoHTTPD.LOG.log(Level.SEVERE, "Could not stop all connections", e);
}
}
public final boolean wasStarted() {
return this.myServerSocket != null && this.myThread != null;
}
static void printMyIPs() {
String ip;
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// filters out 127.0.0.1 and inactive interfaces
if (iface.isLoopback() || !iface.isUp())
continue;
Enumeration addresses = iface.getInetAddresses();
while(addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
ip = addr.getHostAddress();
if (ip.startsWith("127.")) continue;
boolean local = addr.isSiteLocalAddress() || ip.startsWith("fe");
System.out.println(iface.getDisplayName() + " " + ip + " " + (local ? "(private address)" : "(public address)"));
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
static interface IStatus {
String getDescription();
int getRequestStatus();
}
/**
* Some HTTP response status codes
*/
static enum Status implements IStatus {
SWITCH_PROTOCOL(101, "Switching Protocols"),
OK(200, "OK"),
CREATED(201, "Created"),
ACCEPTED(202, "Accepted"),
NO_CONTENT(204, "No Content"),
PARTIAL_CONTENT(206, "Partial Content"),
REDIRECT(301, "Moved Permanently"),
NOT_MODIFIED(304, "Not Modified"),
BAD_REQUEST(400, "Bad Request"),
UNAUTHORIZED(401, "Unauthorized"),
FORBIDDEN(403, "Forbidden"),
NOT_FOUND(404, "Not Found"),
METHOD_NOT_ALLOWED(405, "Method Not Allowed"),
REQUEST_TIMEOUT(408, "Request Timeout"),
RANGE_NOT_SATISFIABLE(416, "Requested Range Not Satisfiable"),
INTERNAL_ERROR(500, "Internal Server Error"),
UNSUPPORTED_HTTP_VERSION(505, "HTTP Version Not Supported");
private final int requestStatus;
private final String description;
Status(int requestStatus, String description) {
this.requestStatus = requestStatus;
this.description = description;
}
@Override
public String getDescription() {
return "" + this.requestStatus + " " + this.description;
}
@Override
public int getRequestStatus() {
return this.requestStatus;
}
}
int getPort() { return myPort; }
public void close() { stop(); }
}
static abstract class F0 {
abstract A get();
}
static abstract class F1 {
abstract B get(A a);
}
// you still need to implement hasNext() and next()
static abstract class IterableIterator implements Iterator, Iterable {
public Iterator iterator() {
return this;
}
public void remove() {
unsupportedOperation();
}
}
static interface IF0 {
A get();
}
static interface Hasher {
int hashCode(A a);
boolean equals(A a, A b);
}
static interface IF1 {
B get(A a);
}
/*
* @(#)WeakHashMap.java 1.5 98/09/30
*
* Copyright 1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
*/
// From https://github.com/mernst/plume-lib/blob/df0bfafc3c16848d88f4ea0ef3c8bf3367ae085e/java/src/plume/WeakHasherMap.java
static final class WeakHasherMap extends AbstractMap implements Map {
private Hasher hasher = null;
/*@Pure*/
private boolean keyEquals(Object k1, Object k2) {
return (hasher==null ? k1.equals(k2)
: hasher.equals(k1, k2));
}
/*@Pure*/
private int keyHashCode(Object k1) {
return (hasher==null ? k1.hashCode()
: hasher.hashCode(k1));
}
// The WeakKey class can't be static because it depends on the hasher.
// That in turn means that its methods can't be static.
// However, I need to be able to call the methods such as create() that
// were static in the original version of this code.
// This finesses that.
private /*@Nullable*/ WeakKey WeakKeyCreate(K k) {
if (k == null) return null;
else return new WeakKey(k);
}
private /*@Nullable*/ WeakKey WeakKeyCreate(K k, ReferenceQueue super K> q) {
if (k == null) return null;
else return new WeakKey(k, q);
}
// Cannot be a static class: uses keyHashCode() and keyEquals()
private final class WeakKey extends WeakReference {
private int hash; /* Hashcode of key, stored here since the key
may be tossed by the GC */
private WeakKey(K k) {
super(k);
hash = keyHashCode(k);
}
private /*@Nullable*/ WeakKey create(K k) {
if (k == null) return null;
else return new WeakKey(k);
}
private WeakKey(K k, ReferenceQueue super K> q) {
super(k, q);
hash = keyHashCode(k);
}
private /*@Nullable*/ WeakKey create(K k, ReferenceQueue super K> q) {
if (k == null) return null;
else return new WeakKey(k, q);
}
/* A WeakKey is equal to another WeakKey iff they both refer to objects
that are, in turn, equal according to their own equals methods */
/*@Pure*/
@Override
public boolean equals(/*@Nullable*/ Object o) {
if (o == null) return false; // never happens
if (this == o) return true;
// This test is illegal because WeakKey is a generic type,
// so use the getClass hack below instead.
// if (!(o instanceof WeakKey)) return false;
if (!(o.getClass().equals(WeakKey.class))) return false;
Object t = this.get();
@SuppressWarnings("unchecked")
Object u = ((WeakKey)o).get();
if ((t == null) || (u == null)) return false;
if (t == u) return true;
return keyEquals(t, u);
}
/*@Pure*/
@Override
public int hashCode() {
return hash;
}
}
/* Hash table mapping WeakKeys to values */
private HashMap hash;
/* Reference queue for cleared WeakKeys */
private ReferenceQueue super K> queue = new ReferenceQueue();
/* Remove all invalidated entries from the map, that is, remove all entries
whose keys have been discarded. This method should be invoked once by
each public mutator in this class. We don't invoke this method in
public accessors because that can lead to surprising
ConcurrentModificationExceptions. */
@SuppressWarnings("unchecked")
private void processQueue() {
WeakKey wk;
while ((wk = (WeakKey)queue.poll()) != null) { // unchecked cast
hash.remove(wk);
}
}
/* -- Constructors -- */
/**
* Constructs a new, empty WeakHashMap
with the given
* initial capacity and the given load factor.
*
* @param initialCapacity the initial capacity of the
* WeakHashMap
*
* @param loadFactor the load factor of the WeakHashMap
*
* @throws IllegalArgumentException If the initial capacity is less than
* zero, or if the load factor is
* nonpositive
*/
public WeakHasherMap(int initialCapacity, float loadFactor) {
hash = new HashMap(initialCapacity, loadFactor);
}
/**
* Constructs a new, empty WeakHashMap
with the given
* initial capacity and the default load factor, which is
* 0.75
.
*
* @param initialCapacity the initial capacity of the
* WeakHashMap
*
* @throws IllegalArgumentException If the initial capacity is less than
* zero
*/
public WeakHasherMap(int initialCapacity) {
hash = new HashMap(initialCapacity);
}
/**
* Constructs a new, empty WeakHashMap
with the default
* capacity and the default load factor, which is 0.75
.
*/
public WeakHasherMap() {
hash = new HashMap();
}
/**
* Constructs a new, empty WeakHashMap
with the default
* capacity and the default load factor, which is 0.75
.
* The WeakHashMap
uses the specified hasher for hashing
* keys and comparing them for equality.
* @param h the Hasher to use when hashing values for this map
*/
public WeakHasherMap(Hasher h) {
hash = new HashMap();
hasher = h;
}
/* -- Simple queries -- */
/**
* Returns the number of key-value mappings in this map.
* Note: In contrast to most implementations of the
* Map
interface, the time required by this operation is
* linear in the size of the map.
*/
/*@Pure*/
@Override
public int size() {
return entrySet().size();
}
/**
* Returns true
if this map contains no key-value mappings.
*/
/*@Pure*/
@Override
public boolean isEmpty() {
return entrySet().isEmpty();
}
/**
* Returns true
if this map contains a mapping for the
* specified key.
*
* @param key the key whose presence in this map is to be tested
*/
/*@Pure*/
@Override
public boolean containsKey(Object key) {
@SuppressWarnings("unchecked")
K kkey = (K) key;
return hash.containsKey(WeakKeyCreate(kkey));
}
/* -- Lookup and modification operations -- */
/**
* Returns the value to which this map maps the specified key
.
* If this map does not contain a value for this key, then return
* null
.
*
* @param key the key whose associated value, if any, is to be returned
*/
/*@Pure*/
@Override
public /*@Nullable*/ V get(Object key) { // type of argument is Object, not K
@SuppressWarnings("unchecked")
K kkey = (K) key;
return hash.get(WeakKeyCreate(kkey));
}
/**
* Updates this map so that the given key
maps to the given
* value
. If the map previously contained a mapping for
* key
then that mapping is replaced and the previous value is
* returned.
*
* @param key the key that is to be mapped to the given
* value
* @param value the value to which the given key
is to be
* mapped
*
* @return the previous value to which this key was mapped, or
* null
if if there was no mapping for the key
*/
@Override
public V put(K key, V value) {
processQueue();
return hash.put(WeakKeyCreate(key, queue), value);
}
/**
* Removes the mapping for the given key
from this map, if
* present.
*
* @param key the key whose mapping is to be removed
*
* @return the value to which this key was mapped, or null
if
* there was no mapping for the key
*/
@Override
public V remove(Object key) { // type of argument is Object, not K
processQueue();
@SuppressWarnings("unchecked")
K kkey = (K) key;
return hash.remove(WeakKeyCreate(kkey));
}
/**
* Removes all mappings from this map.
*/
@Override
public void clear() {
processQueue();
hash.clear();
}
/* -- Views -- */
/* Internal class for entries */
// This can't be static, again because of dependence on hasher.
@SuppressWarnings("TypeParameterShadowing")
private final class Entry implements Map.Entry {
private Map.Entry ent;
private K key; /* Strong reference to key, so that the GC
will leave it alone as long as this Entry
exists */
Entry(Map.Entry ent, K key) {
this.ent = ent;
this.key = key;
}
/*@Pure*/
@Override
public K getKey() {
return key;
}
/*@Pure*/
@Override
public V getValue() {
return ent.getValue();
}
@Override
public V setValue(V value) {
return ent.setValue(value);
}
/*@Pure*/
private boolean keyvalEquals(K o1, K o2) {
return (o1 == null) ? (o2 == null) : keyEquals(o1, o2);
}
/*@Pure*/
private boolean valEquals(V o1, V o2) {
return (o1 == null) ? (o2 == null) : o1.equals(o2);
}
/*@Pure*/
@SuppressWarnings("NonOverridingEquals")
public boolean equals(Map.Entry e /* Object o*/) {
// if (! (o instanceof Map.Entry)) return false;
// Map.Entry e = (Map.Entry)o;
return (keyvalEquals(key, e.getKey())
&& valEquals(getValue(), e.getValue()));
}
/*@Pure*/
@Override
public int hashCode() {
V v;
return (((key == null) ? 0 : keyHashCode(key))
^ (((v = getValue()) == null) ? 0 : v.hashCode()));
}
}
/* Internal class for entry sets */
private final class EntrySet extends AbstractSet> {
Set> hashEntrySet = hash.entrySet();
@Override
public Iterator> iterator() {
return new Iterator>() {
Iterator> hashIterator = hashEntrySet.iterator();
Map.Entry next = null;
@Override
public boolean hasNext() {
while (hashIterator.hasNext()) {
Map.Entry ent = hashIterator.next();
WeakKey wk = ent.getKey();
K k = null;
if ((wk != null) && ((k = wk.get()) == null)) {
/* Weak key has been cleared by GC */
continue;
}
next = new Entry(ent, k);
return true;
}
return false;
}
@Override
public Map.Entry next() {
if ((next == null) && !hasNext())
throw new NoSuchElementException();
Map.Entry e = next;
next = null;
return e;
}
@Override
public void remove() {
hashIterator.remove();
}
};
}
/*@Pure*/
@Override
public boolean isEmpty() {
return !(iterator().hasNext());
}
/*@Pure*/
@Override
public int size() {
int j = 0;
for (Iterator> i = iterator(); i.hasNext(); i.next()) j++;
return j;
}
@Override
public boolean remove(Object o) {
processQueue();
if (!(o instanceof Map.Entry,?>)) return false;
@SuppressWarnings("unchecked")
Map.Entry e = (Map.Entry)o; // unchecked cast
Object ev = e.getValue();
WeakKey wk = WeakKeyCreate(e.getKey());
Object hv = hash.get(wk);
if ((hv == null)
? ((ev == null) && hash.containsKey(wk)) : hv.equals(ev)) {
hash.remove(wk);
return true;
}
return false;
}
/*@Pure*/
@Override
public int hashCode() {
int h = 0;
for (Iterator> i = hashEntrySet.iterator(); i.hasNext(); ) {
Map.Entry ent = i.next();
WeakKey wk = ent.getKey();
Object v;
if (wk == null) continue;
h += (wk.hashCode()
^ (((v = ent.getValue()) == null) ? 0 : v.hashCode()));
}
return h;
}
}
private /*@Nullable*/ Set> entrySet = null;
/**
* Returns a Set
view of the mappings in this map.
*/
/*@SideEffectFree*/
@Override
public Set> entrySet() {
if (entrySet == null) entrySet = new EntrySet();
return entrySet;
}
// find matching key
K findKey(Object key) {
processQueue();
K kkey = (K) key;
// TODO: use replacement for HashMap to avoid reflection
WeakKey wkey = WeakKeyCreate(kkey);
WeakKey found = hashMap_findKey(hash, wkey);
return found == null ? null : found.get();
}
}
static class PersistableThrowable extends DynamicObject {
String className;
String msg;
String stacktrace;
PersistableThrowable() {}
PersistableThrowable(Throwable e) {
if (e == null)
className = "Crazy Null Error";
else {
className = getClassName(e).replace('/', '.');
msg = e.getMessage();
stacktrace = getStackTrace_noRecord(e);
}
}
public String toString() {
return nempty(msg) ? className + ": " + msg : className;
}
}
static class Fail extends RuntimeException implements IFieldsToList{
Object[] objects;
Fail() {}
Fail(Object... objects) {
this.objects = objects;}
public String toString() { return shortClassName_dropNumberPrefix(this) + "(" + objects + ")"; }public Object[] _fieldsToList() { return new Object[] {objects}; }
}
static class Pair implements Comparable> {
A a;
B b;
Pair() {}
Pair(A a, B b) {
this.b = b;
this.a = a;}
public int hashCode() {
return hashCodeFor(a) + 2*hashCodeFor(b);
}
public boolean equals(Object o) {
if (o == this) return true;
if (!(o instanceof Pair)) return false;
Pair t = (Pair) o;
return eq(a, t.a) && eq(b, t.b);
}
public String toString() {
return "<" + a + ", " + b + ">";
}
public int compareTo(Pair p) {
if (p == null) return 1;
int i = ((Comparable) a).compareTo(p.a);
if (i != 0) return i;
return ((Comparable) b).compareTo(p.b);
}
}
static interface IFieldsToList {
Object[] _fieldsToList();
}
static interface IVF1 {
void get(A a);
}
static boolean isAbstract(Class c) {
return (c.getModifiers() & Modifier.ABSTRACT) != 0;
}
static boolean isAbstract(Method m) {
return (m.getModifiers() & Modifier.ABSTRACT) != 0;
}
static boolean reflection_isForbiddenMethod(Method m) {
return m.getDeclaringClass() == Object.class
&& eqOneOf(m.getName(), "finalize", "clone", "registerNatives");
}
static Set allInterfacesImplementedBy(Class c) {
if (c == null) return null;
HashSet set = new HashSet();
allInterfacesImplementedBy_find(c, set);
return set;
}
static void allInterfacesImplementedBy_find(Class c, Set set) {
if (c.isInterface() && !set.add(c)) return;
do {
for (Class intf : c.getInterfaces())
allInterfacesImplementedBy_find(intf, set);
} while ((c = c.getSuperclass()) != null);
}
static Method findMethod(Object o, String method, Object... args) {
return findMethod_cached(o, method, args);
}
static boolean findMethod_checkArgs(Method m, Object[] args, boolean debug) {
Class>[] types = m.getParameterTypes();
if (types.length != args.length) {
if (debug)
System.out.println("Bad parameter length: " + args.length + " vs " + types.length);
return false;
}
for (int i = 0; i < types.length; i++)
if (!(args[i] == null || isInstanceX(types[i], args[i]))) {
if (debug)
System.out.println("Bad parameter " + i + ": " + args[i] + " vs " + types[i]);
return false;
}
return true;
}
static Method findStaticMethod(Class c, String method, Object... args) {
Class _c = c;
while (c != null) {
for (Method m : c.getDeclaredMethods()) {
if (!m.getName().equals(method))
continue;
if ((m.getModifiers() & Modifier.STATIC) == 0 || !findStaticMethod_checkArgs(m, args))
continue;
return m;
}
c = c.getSuperclass();
}
return null;
}
static boolean findStaticMethod_checkArgs(Method m, Object[] args) {
Class>[] types = m.getParameterTypes();
if (types.length != args.length)
return false;
for (int i = 0; i < types.length; i++)
if (!(args[i] == null || isInstanceX(types[i], args[i])))
return false;
return true;
}
static String unquote(String s) {
if (s == null) return null;
if (startsWith(s, '[')) {
int i = 1;
while (i < s.length() && s.charAt(i) == '=') ++i;
if (i < s.length() && s.charAt(i) == '[') {
String m = s.substring(1, i);
if (s.endsWith("]" + m + "]"))
return s.substring(i+1, s.length()-i-1);
}
}
if (s.length() > 1) {
char c = s.charAt(0);
if (c == '\"' || c == '\'') {
int l = endsWith(s, c) ? s.length()-1 : s.length();
StringBuilder sb = new StringBuilder(l-1);
for (int i = 1; i < l; i++) {
char ch = s.charAt(i);
if (ch == '\\') {
char nextChar = (i == l - 1) ? '\\' : s.charAt(i + 1);
// Octal escape?
if (nextChar >= '0' && nextChar <= '7') {
String code = "" + nextChar;
i++;
if ((i < l - 1) && s.charAt(i + 1) >= '0'
&& s.charAt(i + 1) <= '7') {
code += s.charAt(i + 1);
i++;
if ((i < l - 1) && s.charAt(i + 1) >= '0'
&& s.charAt(i + 1) <= '7') {
code += s.charAt(i + 1);
i++;
}
}
sb.append((char) Integer.parseInt(code, 8));
continue;
}
switch (nextChar) {
case '\"': ch = '\"'; break;
case '\\': ch = '\\'; break;
case 'b': ch = '\b'; break;
case 'f': ch = '\f'; break;
case 'n': ch = '\n'; break;
case 'r': ch = '\r'; break;
case 't': ch = '\t'; break;
case '\'': ch = '\''; break;
// Hex Unicode: u????
case 'u':
if (i >= l - 5) {
ch = 'u';
break;
}
int code = Integer.parseInt(
"" + s.charAt(i + 2) + s.charAt(i + 3)
+ s.charAt(i + 4) + s.charAt(i + 5), 16);
sb.append(Character.toChars(code));
i += 5;
continue;
default:
ch = nextChar; // added by Stefan
}
i++;
}
sb.append(ch);
}
return sb.toString();
}
}
return s; // not quoted - return original
}
static List quoteAll(Collection l) {
List x = new ArrayList();
for (String s : l)
x.add(quote(s));
return x;
}
static int _hashCode(Object a) {
return a == null ? 0 : a.hashCode();
}
static ArrayList toList(A[] a) { return asList(a); }
static ArrayList toList(int[] a) { return asList(a); }
static ArrayList toList(Set s) { return asList(s); }
static ArrayList toList(Iterable s) { return asList(s); }
static boolean arraysEqual(Object[] a, Object[] b) {
if (a.length != b.length) return false;
for (int i = 0; i < a.length; i++)
if (neq(a[i], b[i])) return false;
return true;
}
static int hashCode(Object a) {
return a == null ? 0 : a.hashCode();
}
static boolean nempty(Collection c) {
return !empty(c);
}
static boolean nempty(CharSequence s) {
return !empty(s);
}
static boolean nempty(Object[] o) { return !empty(o); }
static boolean nempty(byte[] o) { return !empty(o); }
static boolean nempty(int[] o) { return !empty(o); }
static boolean nempty(Map m) {
return !empty(m);
}
static boolean nempty(Iterator i) {
return i != null && i.hasNext();
}
static boolean nempty(Object o) { return !empty(o); }
static A set(A o, String field, Object value) {
if (o == null) return null;
if (o instanceof Class) set((Class) o, field, value);
else try {
Field f = set_findField(o.getClass(), field);
makeAccessible(f);
smartSet(f, o, value);
} catch (Exception e) {
throw new RuntimeException(e);
}
return o;
}
static void set(Class c, String field, Object value) {
if (c == null) return;
try {
Field f = set_findStaticField(c, field);
makeAccessible(f);
smartSet(f, null, value);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
static Field set_findStaticField(Class> c, String field) {
Class _c = c;
do {
for (Field f : _c.getDeclaredFields())
if (f.getName().equals(field) && (f.getModifiers() & java.lang.reflect.Modifier.STATIC) != 0)
return f;
_c = _c.getSuperclass();
} while (_c != null);
throw new RuntimeException("Static field '" + field + "' not found in " + c.getName());
}
static Field set_findField(Class> c, String field) {
Class _c = c;
do {
for (Field f : _c.getDeclaredFields())
if (f.getName().equals(field))
return f;
_c = _c.getSuperclass();
} while (_c != null);
throw new RuntimeException("Field '" + field + "' not found in " + c.getName());
}
static void set(BitSet bs, int idx) {
{ if (bs != null) bs.set(idx); }
}
static Iterator iterator(Iterable c) {
return c == null ? emptyIterator() : c.iterator();
}
static A addAndReturn(Collection c, A a) {
if (c != null) c.add(a);
return a;
}
static volatile PersistableThrowable _handleException_lastException;
static List _handleException_onException = synchroList(ll((IVF1) (__1 -> printStackTrace2(__1))));
static boolean _handleException_showThreadCancellations = false;
static void _handleException(Throwable e) {
_handleException_lastException = persistableThrowable(e);
Throwable e2 = innerException(e);
if (e2.getClass() == RuntimeException.class && eq(e2.getMessage(), "Thread cancelled.") || e2 instanceof InterruptedException) {
if (_handleException_showThreadCancellations)
System.out.println(getStackTrace_noRecord(e2));
return;
}
for (Object f : cloneList(_handleException_onException)) try {
callF(f, e);
} catch (Throwable e3) {
try {
printStackTrace2(e3); // not using pcall here - it could lead to endless loops
} catch (Throwable e4) {
System.out.println(getStackTrace(e3));
System.out.println(getStackTrace(e4));
}
}
}
static File tempDir() {
return makeTempDir();
}
static File createTempFile() {
return createTempFile("tmp", null);
}
static File createTempFile(String prefix, String suffix) { try {
prefix = nohup_sanitize(prefix);
suffix = nohup_sanitize(suffix);
if (shouldKeepTempFiles())
return mkdirsForFile(javaxCachesDir("Temp/" + prefix + "-" + randomID() + "-" + suffix));
// File.createTempFile needs at least 3 prefix characters, so we
// fíll them up for you
File f = File.createTempFile(takeFirst(10, pad(prefix, 3, '-')), suffix);
f.deleteOnExit();
return f;
} catch (Exception __e) { throw rethrow(__e); } }
static long sysNow() {
ping();
return System.nanoTime()/1000000;
}
static A printWithIndent(A o) {
return printIndent(o);
}
static A printWithIndent(String indent, A o) {
return printIndent(indent, o);
}
static void printWithIndent(int indent, Object o) {
printIndent(indent, o);
}
static HashMap lithashmap(Object... x) {
return litmap(x);
}
// name currently not used, we only call a method called "_wrap" in
// the main class with the to-wrapped object as first argument and
// a list of other parameters as second argument.
static A wrapStuff(String name, A object, Object... args) {
//ret or(object, (A) callOpt(mc(), "wrap" + name,
// toObjectArray(concatLists(litlist((O) object), toList(args)))));
return or((A) callOpt(mc(), "_wrap", object, asList(args)), object);
}
static void printMyIPs() {
String ip;
try {
Enumeration interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface iface = interfaces.nextElement();
// filters out 127.0.0.1 and inactive interfaces
if (iface.isLoopback() || !iface.isUp())
continue;
Enumeration addresses = iface.getInetAddresses();
while(addresses.hasMoreElements()) {
InetAddress addr = addresses.nextElement();
ip = addr.getHostAddress();
if (ip.startsWith("127.")) continue;
boolean local = addr.isSiteLocalAddress() || ip.startsWith("fe");
System.out.println(iface.getDisplayName() + " " + ip + " " + (local ? "(private address)" : "(public address)"));
}
}
} catch (Throwable e) {
e.printStackTrace();
}
}
static UnsupportedOperationException unsupportedOperation() {
throw new UnsupportedOperationException();
}
static Set> entrySet(Map map) {
return _entrySet(map);
}
static void remove(List l, int i) {
if (l != null && i >= 0 && i < l(l))
l.remove(i);
}
static void remove(Collection l, A a) {
if (l != null) l.remove(a);
}
static B remove(Map map, Object a) {
return map == null ? null : map.remove(a);
}
static Method hashMap_findKey_method;
static A hashMap_findKey(HashMap map, Object key) { try {
if (hashMap_findKey_method == null)
hashMap_findKey_method = findMethodNamed(HashMap.class, "getNode");
Map.Entry entry = (Map.Entry) hashMap_findKey_method.invoke(map, hashMap_internalHash(key), key);
// java.util.Map.Entry entry = (java.util.Map.Entry) call(hash, 'getNode, hashMap_internalHash(key), wkey);
return entry == null ? null : entry.getKey();
} catch (Exception __e) { throw rethrow(__e); } }
static String shortClassName_dropNumberPrefix(Object o) {
return dropNumberPrefix(shortClassName(o));
}
static int hashCodeFor(Object a) {
return a == null ? 0 : a.hashCode();
}
static Method findMethod_cached(Object o, String method, Object... args) { try {
if (o == null) return null;
if (o instanceof Class) {
_MethodCache cache = callOpt_getCache((Class) o);
List methods = cache.cache.get(method);
if (methods != null) for (Method m : methods)
if (isStaticMethod(m) && findMethod_checkArgs(m, args, false))
return m;
return null;
} else {
_MethodCache cache = callOpt_getCache(o.getClass());
List methods = cache.cache.get(method);
if (methods != null) for (Method m : methods)
if (findMethod_checkArgs(m, args, false))
return m;
return null;
}
} catch (Exception __e) { throw rethrow(__e); } }
static boolean endsWith(String a, String b) {
return a != null && a.endsWith(b);
}
static boolean endsWith(String a, char c) {
return nempty(a) && lastChar(a) == c;
}
static boolean endsWith(String a, String b, Matches m) {
if (!endsWith(a, b)) return false;
m.m = new String[] {dropLast(l(b), a)};
return true;
}
static String quote(Object o) {
if (o == null) return "null";
return quote(str(o));
}
static String quote(String s) {
if (s == null) return "null";
StringBuilder out = new StringBuilder((int) (l(s)*1.5+2));
quote_impl(s, out);
return out.toString();
}
static void quote_impl(String s, StringBuilder out) {
out.append('"');
int l = s.length();
for (int i = 0; i < l; i++) {
char c = s.charAt(i);
if (c == '\\' || c == '"')
out.append('\\').append(c);
else if (c == '\r')
out.append("\\r");
else if (c == '\n')
out.append("\\n");
else if (c == '\t')
out.append("\\t");
else if (c == '\0')
out.append("\\0");
else
out.append(c);
}
out.append('"');
}
static void smartSet(Field f, Object o, Object value) throws Exception {
try {
f.set(o, value);
} catch (Exception e) {
Class type = f.getType();
// take care of common case (long to int)
if (type == int.class && value instanceof Long)
{ f.set(o, ((Long) value).intValue()); return; }
if (type == boolean.class && value instanceof String)
{ f.set(o, isTrueOrYes(((String) value))); return; }
if (type == LinkedHashMap.class && value instanceof Map)
{ f.set(o, asLinkedHashMap((Map) value)); return; }
throw e;
}
}
static Iterator emptyIterator() {
return Collections.emptyIterator();
}
static Throwable printStackTrace2(Throwable e) {
// we go to system.out now - system.err is nonsense
print(getStackTrace2(e));
return e;
}
static void printStackTrace2() {
printStackTrace2(new Throwable());
}
static void printStackTrace2(String msg) {
printStackTrace2(new Throwable(msg));
}
static Throwable innerException(Throwable e) {
return getInnerException(e);
}
static File makeTempDir() {
return (File) call(getJavaX(), "TempDirMaker_make");
}
static String nohup_sanitize(String s) {
return empty(s) ? s : takeFirst(50, s.replaceAll("[^.a-zA-Z0-9\\-_]", ""));
}
static IF0 shouldKeepTempFiles;
static boolean shouldKeepTempFiles() { return shouldKeepTempFiles != null ? shouldKeepTempFiles.get() : shouldKeepTempFiles_base(); }
final static boolean shouldKeepTempFiles_fallback(IF0 _f) { return _f != null ? _f.get() : shouldKeepTempFiles_base(); }
static boolean shouldKeepTempFiles_base() {
return false;
}
public static File mkdirsForFile(File file) {
File dir = file.getParentFile();
if (dir != null) { // is null if file is in current dir
dir.mkdirs();
if (!dir.isDirectory())
if (dir.isFile()) throw fail("Please delete the file " + f2s(dir) + " - it is supposed to be a directory!");
else throw fail("Unknown IO exception during mkdirs of " + f2s(file));
}
return file;
}
public static String mkdirsForFile(String path) {
mkdirsForFile(new File(path));
return path;
}
static File javaxCachesDir_dir; // can be set to work on different base dir
static File javaxCachesDir() {
return javaxCachesDir_dir != null ? javaxCachesDir_dir : new File(userHome(), "JavaX-Caches");
}
static File javaxCachesDir(String sub) {
return newFile(javaxCachesDir(), sub);
}
static int randomID_defaultLength = 12;
static String randomID(int length) {
return makeRandomID(length);
}
static String randomID(Random r, int length) {
return makeRandomID(r, length);
}
static String randomID() {
return randomID(randomID_defaultLength);
}
static String randomID(Random r) {
return randomID(r, randomID_defaultLength);
}
static List takeFirst(List l, int n) {
return l(l) <= n ? l : newSubListOrSame(l, 0, n);
}
static List takeFirst(int n, List l) {
return takeFirst(l, n);
}
static String takeFirst(int n, String s) { return substring(s, 0, n); }
static String takeFirst(String s, int n) { return substring(s, 0, n); }
static CharSequence takeFirst(int n, CharSequence s) { return subCharSequence(s, 0, n); }
static List takeFirst(int n, Iterator it) {
if (it == null) return null;
List l = new ArrayList();
for (int _repeat_0 = 0; _repeat_0 < n; _repeat_0++) { if (it.hasNext()) l.add(it.next()); else break; }
return l;
}
static List takeFirst(int n, Iterable i) {
if (i == null) return null;
return i == null ? null : takeFirst(n, i.iterator());
}
static List takeFirst(int n, IterableIterator i) {
return takeFirst(n, (Iterator) i);
}
static int[] takeFirst(int n, int[] a) {
return takeFirstOfIntArray(n, a);
}
static String pad(Object s, int l) {
return pad(s, l, ' ');
}
static String pad(Object s, int l, char c) {
String _s = str(s);
if (lengthOfString(_s) >= l) return _s;
return rep(c, l-lengthOfString(_s)) + _s;
}
static String pad(int l, Object s) {
return pad(s, l);
}
static A printIndent(A o) {
print(indentx(str(o)));
return o;
}
static A printIndent(String indent, A o) {
print(indentx(indent, str(o)));
return o;
}
static void printIndent(int indent, Object o) {
print(indentx(indent, str(o)));
}
static HashMap litmap(Object... x) {
HashMap map = new HashMap();
litmap_impl(map, x);
return map;
}
static void litmap_impl(Map map, Object... x) {
if (x != null) for (int i = 0; i < x.length-1; i += 2)
if (x[i+1] != null)
map.put(x[i], x[i+1]);
}
static Object callOpt(Object o) {
return callF(o);
}
static A callOpt(Object o, String method, Object... args) {
return (A) callOpt_withVarargs(o, method, args);
}
static Set> _entrySet(Map map) {
return map == null ? Collections.EMPTY_SET : map.entrySet();
}
// This is a bit rough... finds static and non-static methods.
static Method findMethodNamed(Object obj, String method) {
if (obj == null) return null;
if (obj instanceof Class)
return findMethodNamed((Class) obj, method);
return findMethodNamed(obj.getClass(), method);
}
static Method findMethodNamed(Class c, String method) {
while (c != null) {
for (Method m : c.getDeclaredMethods())
if (m.getName().equals(method)) {
makeAccessible(m);
return m;
}
c = c.getSuperclass();
}
return null;
}
static int hashMap_internalHash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
static String dropNumberPrefix(String s) {
return dropFirst(s, indexOfNonDigit(s));
}
static String shortClassName(Object o) {
if (o == null) return null;
Class c = o instanceof Class ? (Class) o : o.getClass();
String name = c.getName();
return shortenClassName(name);
}
static char lastChar(String s) {
return empty(s) ? '\0' : s.charAt(l(s)-1);
}
static A[] dropLast(A[] a) { return dropLast(a, 1); }
static A[] dropLast(A[] a, int n) {
if (a == null) return null;
n = Math.min(n, a.length);
A[] b = arrayOfSameType(a, a.length-n);
System.arraycopy(a, 0, b, 0, b.length);
return b;
}
static List dropLast(List l) {
return subList(l, 0, l(l)-1);
}
static List dropLast(int n, List l) {
return subList(l, 0, l(l)-n);
}
static List dropLast(Iterable l) {
return dropLast(asList(l));
}
static String dropLast(String s) {
return substring(s, 0, l(s)-1);
}
static String dropLast(String s, int n) {
return substring(s, 0, l(s)-n);
}
static String dropLast(int n, String s) {
return dropLast(s, n);
}
static boolean isTrueOrYes(Object o) {
return isTrueOpt(o) || o instanceof String && (eqicOneOf(((String) o), "1", "t", "true") || isYes(((String) o)));
}
static LinkedHashMap asLinkedHashMap(Map map) {
if (map instanceof LinkedHashMap) return (LinkedHashMap) map;
LinkedHashMap m = new LinkedHashMap();
if (map != null) synchronized(collectionMutex(map)) {
m.putAll(map);
}
return m;
}
static String getStackTrace2(Throwable e) {
return hideCredentials(getStackTrace(unwrapTrivialExceptionWraps(e)) + replacePrefix("java.lang.RuntimeException: ", "FAIL: ",
hideCredentials(str(innerException2(e)))) + "\n");
}
static Throwable getInnerException(Throwable e) {
if (e == null) return null;
while (e.getCause() != null)
e = e.getCause();
return e;
}
static Throwable getInnerException(Runnable r) {
return getInnerException(getException(r));
}
static String f2s(File f) {
return f == null ? null : f.getAbsolutePath();
}
static String f2s(String s) { return f2s(newFile(s)); }
static String f2s(java.nio.file.Path p) {
return p == null ? null : f2s(p.toFile());
}
static String _userHome;
static String userHome() {
if (_userHome == null)
return actualUserHome();
return _userHome;
}
static File userHome(String path) {
return new File(userDir(), path);
}
static File newFile(File base, String... names) {
for (String name : names) base = new File(base, name);
return base;
}
static File newFile(String name) {
return name == null ? null : new File(name);
}
static File newFile(String base, String... names) {
return newFile(newFile(base), names);
}
static String makeRandomID(int length) {
return makeRandomID(length, defaultRandomGenerator());
}
static String makeRandomID(int length, Random random) {
char[] id = new char[length];
for (int i = 0; i < id.length; i++)
id[i] = (char) ((int) 'a' + random.nextInt(26));
return new String(id);
}
static String makeRandomID(Random r, int length) {
return makeRandomID(length, r);
}
static List newSubListOrSame(List l, int startIndex) {
return newSubListOrSame(l, startIndex, l(l));
}
static List newSubListOrSame(List l, int startIndex, int endIndex) {
if (l == null) return null;
int n = l(l);
startIndex = max(0, startIndex);
endIndex = min(n, endIndex);
if (startIndex >= endIndex) return ll();
if (startIndex == 0 && endIndex == n) return l;
return cloneList(l.subList(startIndex, endIndex));
}
static CharSequence subCharSequence(CharSequence s, int x) {
return subCharSequence(s, x, s == null ? 0 : s.length());
}
static CharSequence subCharSequence(CharSequence s, int x, int y) {
if (s == null) return null;
if (x < 0) x = 0;
if (x >= s.length()) return "";
if (y < x) y = x;
if (y > s.length()) y = s.length();
return s.subSequence(x, y);
}
static int[] takeFirstOfIntArray(int[] b, int n) {
return subIntArray(b, 0, n);
}
static int[] takeFirstOfIntArray(int n, int[] b) {
return takeFirstOfIntArray(b, n);
}
static int lengthOfString(String s) {
return s == null ? 0 : s.length();
}
static String rep(int n, char c) {
return repeat(c, n);
}
static String rep(char c, int n) {
return repeat(c, n);
}
static List rep(A a, int n) {
return repeat(a, n);
}
static List rep(int n, A a) {
return repeat(n, a);
}
static String indentx(String s) {
return indentx(indent_default, s);
}
static String indentx(int n, String s) {
return dropSuffix(repeat(' ', n), indent(n, s));
}
static String indentx(String indent, String s) {
return dropSuffix(indent, indent(indent, s));
}
static Object callOpt_withVarargs(Object o, String method, Object... args) { try {
if (o == null) return null;
if (o instanceof Class) {
Class c = (Class) o;
_MethodCache cache = callOpt_getCache(c);
Method me = cache.findMethod(method, args);
if (me == null) {
// TODO: varargs
return null;
}
if ((me.getModifiers() & Modifier.STATIC) == 0)
return null;
return invokeMethod(me, null, args);
} else {
Class c = o.getClass();
_MethodCache cache = callOpt_getCache(c);
Method me = cache.findMethod(method, args);
if (me != null)
return invokeMethod(me, o, args);
// try varargs
List methods = cache.cache.get(method);
if (methods != null) methodSearch: for (Method m : methods) {
{ if (!(m.isVarArgs())) continue; }
Object[] newArgs = massageArgsForVarArgsCall(m, args);
if (newArgs != null)
return invokeMethod(m, o, newArgs);
}
return null;
}
} catch (Exception __e) { throw rethrow(__e); } }
static String[] dropFirst(int n, String[] a) {
return drop(n, a);
}
static String[] dropFirst(String[] a) {
return drop(1, a);
}
static Object[] dropFirst(Object[] a) {
return drop(1, a);
}
static List dropFirst(List l) {
return dropFirst(1, l);
}
static List dropFirst(int n, Iterable i) { return dropFirst(n, toList(i)); }
static List dropFirst(Iterable i) { return dropFirst(toList(i)); }
static List dropFirst(int n, List l) {
return n <= 0 ? l : new ArrayList(l.subList(Math.min(n, l.size()), l.size()));
}
static List dropFirst(List l, int n) {
return dropFirst(n, l);
}
static String dropFirst(int n, String s) { return substring(s, n); }
static String dropFirst(String s, int n) { return substring(s, n); }
static String dropFirst(String s) { return substring(s, 1); }
static int indexOfNonDigit(String s) {
int n = l(s);
for (int i = 0; i < n; i++)
if (!isDigit(s.charAt(i)))
return i;
return -1;
}
static String shortenClassName(String name) {
if (name == null) return null;
int i = lastIndexOf(name, "$");
if (i < 0) i = lastIndexOf(name, ".");
return i < 0 ? name : substring(name, i+1);
}
static A[] arrayOfSameType(A[] a, int n) {
return newObjectArrayOfSameType(a, n);
}
static List subList(List l, int startIndex) {
return subList(l, startIndex, l(l));
}
static List subList(int startIndex, List l) {
return subList(l, startIndex);
}
static List subList(int startIndex, int endIndex, List l) {
return subList(l, startIndex, endIndex);
}
static List subList(List l, int startIndex, int endIndex) {
if (l == null) return null;
int n = l(l);
startIndex = Math.max(0, startIndex);
endIndex = Math.min(n, endIndex);
if (startIndex > endIndex) return ll();
if (startIndex == 0 && endIndex == n) return l;
return l.subList(startIndex, endIndex);
}
static boolean isTrueOpt(Object o) {
if (o instanceof Boolean)
return ((Boolean) o).booleanValue();
return false;
}
static boolean isTrueOpt(String field, Object o) {
return isTrueOpt(getOpt(field, o));
}
static boolean eqicOneOf(String s, String... l) {
for (String x : l) if (eqic(s, x)) return true; return false;
}
static List isYes_yesses = litlist("y", "yes", "yeah", "y", "yup", "yo", "corect", "sure", "ok", "afirmative"); // << collapsed words, so "corect" means "correct"
static boolean isYes(String s) {
return isYes_yesses.contains(collapseWord(toLowerCase(firstWord2(s))));
}
static Throwable unwrapTrivialExceptionWraps(Throwable e) {
if (e == null) return e;
while (e.getClass() == RuntimeException.class
&& e.getCause() != null && eq(e.getMessage(), str(e.getCause())))
e = e.getCause();
return e;
}
static String replacePrefix(String prefix, String replacement, String s) {
if (!startsWith(s, prefix)) return s;
return replacement + substring(s, l(prefix));
}
static Throwable innerException2(Throwable e) {
if (e == null) return null;
while (empty(e.getMessage()) && e.getCause() != null)
e = e.getCause();
return e;
}
static Throwable getException(Runnable r) {
try {
callF(r);
return null;
} catch (Throwable e) {
return e;
}
}
static String actualUserHome_value;
static String actualUserHome() {
if (actualUserHome_value == null) {
if (isAndroid())
actualUserHome_value = "/storage/emulated/0/";
else
actualUserHome_value = System.getProperty("user.home");
}
return actualUserHome_value;
}
static File actualUserHome(String sub) {
return newFile(new File(actualUserHome()), sub);
}
static File userDir() {
return new File(userHome());
}
static File userDir(String path) {
return new File(userHome(), path);
}
static Random defaultRandomGenerator() {
return ThreadLocalRandom.current();
}
static int min(int a, int b) {
return Math.min(a, b);
}
static long min(long a, long b) {
return Math.min(a, b);
}
static float min(float a, float b) { return Math.min(a, b); }
static float min(float a, float b, float c) { return min(min(a, b), c); }
static double min(double a, double b) {
return Math.min(a, b);
}
static double min(double[] c) {
double x = Double.MAX_VALUE;
for (double d : c) x = Math.min(x, d);
return x;
}
static float min(float[] c) {
float x = Float.MAX_VALUE;
for (float d : c) x = Math.min(x, d);
return x;
}
static byte min(byte[] c) {
byte x = 127;
for (byte d : c) if (d < x) x = d;
return x;
}
static short min(short[] c) {
short x = 0x7FFF;
for (short d : c) if (d < x) x = d;
return x;
}
static int min(int[] c) {
int x = Integer.MAX_VALUE;
for (int d : c) if (d < x) x = d;
return x;
}
static int[] subIntArray(int[] b, int start) {
return subIntArray(b, start, l(b));
}
static int[] subIntArray(int[] b, int start, int end) {
start = max(start, 0); end = min(end, l(b));
if (start == 0 && end == l(b)) return b;
if (start >= end) return new int[0];
int[] x = new int[end-start];
System.arraycopy(b, start, x, 0, end-start);
return x;
}
static String repeat(char c, int n) {
n = Math.max(n, 0);
char[] chars = new char[n];
for (int i = 0; i < n; i++)
chars[i] = c;
return new String(chars);
}
static List repeat(A a, int n) {
n = Math.max(n, 0);
List l = new ArrayList(n);
for (int i = 0; i < n; i++)
l.add(a);
return l;
}
static List repeat(int n, A a) {
return repeat(a, n);
}
static String dropSuffix(String suffix, String s) {
return s.endsWith(suffix) ? s.substring(0, l(s)-l(suffix)) : s;
}
static int indent_default = 2;
static String indent(int indent) {
return repeat(' ', indent);
}
static String indent(int indent, String s) {
return indent(repeat(' ', indent), s);
}
static String indent(String indent, String s) {
return indent + s.replace("\n", "\n" + indent);
}
static String indent(String s) {
return indent(indent_default, s);
}
static List indent(String indent, List lines) {
List l = new ArrayList();
if (lines != null) for (String s : lines)
l.add(indent + s);
return l;
}
static String[] drop(int n, String[] a) {
n = Math.min(n, a.length);
String[] b = new String[a.length-n];
System.arraycopy(a, n, b, 0, b.length);
return b;
}
static Object[] drop(int n, Object[] a) {
n = Math.min(n, a.length);
Object[] b = new Object[a.length-n];
System.arraycopy(a, n, b, 0, b.length);
return b;
}
static boolean isDigit(char c) {
return Character.isDigit(c);
}
static int lastIndexOf(String a, String b) {
return a == null || b == null ? -1 : a.lastIndexOf(b);
}
static int lastIndexOf(String a, char b) {
return a == null ? -1 : a.lastIndexOf(b);
}
// starts searching from i-1
static int lastIndexOf(List l, int i, A a) {
if (l == null) return -1;
for (i = min(l(l), i)-1; i >= 0; i--)
if (eq(l.get(i), a))
return i;
return -1;
}
static int lastIndexOf(List l, A a) {
if (l == null) return -1;
for (int i = l(l)-1; i >= 0; i--)
if (eq(l.get(i), a))
return i;
return -1;
}
static A[] newObjectArrayOfSameType(A[] a) { return newObjectArrayOfSameType(a, a.length); }
static A[] newObjectArrayOfSameType(A[] a, int n) {
return (A[]) Array.newInstance(a.getClass().getComponentType(), n);
}
static ArrayList litlist(A... a) {
ArrayList l = new ArrayList(a.length);
for (A x : a) l.add(x);
return l;
}
static String collapseWord(String s) {
if (s == null) return "";
StringBuilder buf = new StringBuilder();
for (int i = 0; i < l(s); i++)
if (i == 0 || !charactersEqualIC(s.charAt(i), s.charAt(i-1)))
buf.append(s.charAt(i));
return buf.toString();
}
static List toLowerCase(List strings) {
List x = new ArrayList();
for (String s : strings)
x.add(s.toLowerCase());
return x;
}
static String[] toLowerCase(String[] strings) {
String[] x = new String[l(strings)];
for (int i = 0; i < l(strings); i++)
x[i] = strings[i].toLowerCase();
return x;
}
static String toLowerCase(String s) {
return s == null ? "" : s.toLowerCase();
}
static String firstWord2(String s) {
s = xltrim(s);
if (empty(s)) return "";
if (isLetterOrDigit(first(s)))
return takeCharsWhile(__26 -> isLetterOrDigit(__26), s);
else return "" + first(s);
}
static boolean charactersEqualIC(char c1, char c2) {
if (c1 == c2) return true;
char u1 = Character.toUpperCase(c1);
char u2 = Character.toUpperCase(c2);
if (u1 == u2) return true;
return Character.toLowerCase(u1) == Character.toLowerCase(u2);
}
static String xltrim(String s) {
int i = 0, n = l(s);
while (i < n && contains(" \t\r\n", s.charAt(i)))
++i;
return substr(s, i);
}
static boolean isLetterOrDigit(char c) {
return Character.isLetterOrDigit(c);
}
// pred: char -> bool
static String takeCharsWhile(String s, Object pred) {
int i = 0;
while (i < l(s) && isTrue(callF(pred, s.charAt(i)))) ++i;
return substring(s, 0, i);
}
static String takeCharsWhile(IF1 f, String s) {
return takeCharsWhile(s, f);
}
static boolean contains(Collection c, Object o) {
return c != null && c.contains(o);
}
static boolean contains(Object[] x, Object o) {
if (x != null)
for (Object a : x)
if (eq(a, o))
return true;
return false;
}
static boolean contains(String s, char c) {
return s != null && s.indexOf(c) >= 0;
}
static boolean contains(String s, String b) {
return s != null && s.indexOf(b) >= 0;
}
static boolean contains(BitSet bs, int i) {
return bs != null && bs.get(i);
}
static String substr(String s, int x) {
return substring(s, x);
}
static String substr(String s, int x, int y) {
return substring(s, x, y);
}
}