/**
 * Returns an interned string for the given UTF-8 string.
 *
 * @param s null-terminated string to intern
 * @returns interned Java string equivelent of s or NULL if s is null
 */
static jstring internString(JNIEnv* env, ParsingContext* parsingContext, const char* s) {
    if (s == NULL) return NULL;

    int hash = hashString(s);
    int bucketIndex = hash & (BUCKET_COUNT - 1);

    InternedString*** buckets = parsingContext->internedStrings;
    InternedString** bucket = buckets[bucketIndex];
    InternedString* internedString;

    if (bucket) {
        // We have a bucket already. Look for the given string.
        jstring found = findInternedString(bucket, s, hash);
        if (found) {
            // We found it!
            return found;
        }

        // We didn't find it. :(
        // Create a new entry.
        internedString = newInternedString(env, s, hash);
        if (internedString == NULL) return NULL;

        // Expand the bucket.
        bucket = expandInternedStringBucket(bucket, internedString);
        if (bucket == NULL) {
            delete internedString;
            jniThrowOutOfMemoryError(env, NULL);
            return NULL;
        }

        buckets[bucketIndex] = bucket;

        return internedString->interned;
    } else {
        // We don't even have a bucket yet. Create an entry.
        internedString = newInternedString(env, s, hash);
        if (internedString == NULL) return NULL;

        // Create a new bucket with one entry.
        bucket = newInternedStringBucket(internedString);
        if (bucket == NULL) {
            delete internedString;
            jniThrowOutOfMemoryError(env, NULL);
            return NULL;
        }

        buckets[bucketIndex] = bucket;

        return internedString->interned;
    }
}
/**
 * Creates a new Expat parser. Called from the Java ExpatParser constructor.
 *
 * @param object the Java ExpatParser instance
 * @param javaEncoding the character encoding name
 * @param processNamespaces true if the parser should handle namespaces
 * @returns the pointer to the C Expat parser
 */
static jlong ExpatParser_initialize(JNIEnv* env, jobject object, jstring javaEncoding,
        jboolean processNamespaces) {
    // Allocate parsing context.
    UniquePtr<ParsingContext> context(new ParsingContext(object));
    if (context.get() == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
        return 0;
    }

    context->processNamespaces = processNamespaces;

    // Create a parser.
    XML_Parser parser;
    ScopedUtfChars encoding(env, javaEncoding);
    if (encoding.c_str() == NULL) {
        return 0;
    }
    if (processNamespaces) {
        // Use '|' to separate URIs from local names.
        parser = XML_ParserCreateNS(encoding.c_str(), '|');
    } else {
        parser = XML_ParserCreate(encoding.c_str());
    }

    if (parser != NULL) {
        if (processNamespaces) {
            XML_SetNamespaceDeclHandler(parser, startNamespace, endNamespace);
            XML_SetReturnNSTriplet(parser, 1);
        }

        XML_SetCdataSectionHandler(parser, startCdata, endCdata);
        XML_SetCharacterDataHandler(parser, text);
        XML_SetCommentHandler(parser, comment);
        XML_SetDoctypeDeclHandler(parser, startDtd, endDtd);
        XML_SetElementHandler(parser, startElement, endElement);
        XML_SetExternalEntityRefHandler(parser, handleExternalEntity);
        XML_SetNotationDeclHandler(parser, notationDecl);
        XML_SetProcessingInstructionHandler(parser, processingInstruction);
        XML_SetUnparsedEntityDeclHandler(parser, unparsedEntityDecl);
        XML_SetUserData(parser, context.release());
    } else {
        jniThrowOutOfMemoryError(env, NULL);
        return 0;
    }

    return fromXMLParser(parser);
}
/**
 * Creates a new interned string wrapper. Looks up the interned string
 * representing the given UTF-8 bytes.
 *
 * @param bytes null-terminated string to intern
 * @param hash of bytes
 * @returns wrapper of interned Java string
 */
static InternedString* newInternedString(JNIEnv* env, const char* bytes, int hash) {
    // Allocate a new wrapper.
    UniquePtr<InternedString> wrapper(new InternedString);
    if (wrapper.get() == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
        return NULL;
    }

    // Create a copy of the UTF-8 bytes.
    // TODO: sometimes we already know the length. Reuse it if so.
    char* copy = new char[strlen(bytes) + 1];
    if (copy == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
        return NULL;
    }
    strcpy(copy, bytes);
    wrapper->bytes = copy;

    // Save the hash.
    wrapper->hash = hash;

    // To intern a string, we must first create a new string and then call
    // intern() on it. We then keep a global reference to the interned string.
    ScopedLocalRef<jstring> newString(env, env->NewStringUTF(bytes));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    // Call intern().
    ScopedLocalRef<jstring> interned(env,
            reinterpret_cast<jstring>(env->CallObjectMethod(newString.get(), internMethod)));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    // Create a global reference to the interned string.
    wrapper->interned = reinterpret_cast<jstring>(env->NewGlobalRef(interned.get()));
    if (env->ExceptionCheck()) {
        return NULL;
    }

    return wrapper.release();
}
/**
 * Creates a new entity parser.
 *
 * @param object the Java ExpatParser instance
 * @param parentParser pointer
 * @param javaEncoding the character encoding name
 * @param javaContext that was provided to handleExternalEntity
 * @returns the pointer to the C Expat entity parser
 */
static jlong ExpatParser_createEntityParser(JNIEnv* env, jobject, jlong parentParser, jstring javaContext) {
    ScopedUtfChars context(env, javaContext);
    if (context.c_str() == NULL) {
        return 0;
    }

    XML_Parser parent = toXMLParser(parentParser);
    XML_Parser entityParser = XML_ExternalEntityParserCreate(parent, context.c_str(), NULL);
    if (entityParser == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
    }

    return fromXMLParser(entityParser);
}
    void push(JNIEnv* env, jstring s) {
        if (size == capacity) {
            int newCapacity = capacity * 2;
            jstring* newArray = new jstring[newCapacity];
            if (newArray == NULL) {
                jniThrowOutOfMemoryError(env, NULL);
                return;
            }
            memcpy(newArray, array, capacity * sizeof(jstring));

            delete[] array;
            array = newArray;
            capacity = newCapacity;
        }

        array[size++] = s;
    }
static bool throwExceptionIfNecessary(JNIEnv* env) {
  long error = ERR_get_error();
  if (error == 0) {
    return false;
  }
  char message[256];
  ERR_error_string_n(error, message, sizeof(message));
  int reason = ERR_GET_REASON(error);
  if (reason == BN_R_DIV_BY_ZERO) {
    jniThrowException(env, "java/lang/ArithmeticException", "BigInteger division by zero");
  } else if (reason == BN_R_NO_INVERSE) {
    jniThrowException(env, "java/lang/ArithmeticException", "BigInteger not invertible");
  } else if (reason == ERR_R_MALLOC_FAILURE) {
    jniThrowOutOfMemoryError(env, message);
  } else {
    jniThrowException(env, "java/lang/ArithmeticException", message);
  }
  return true;
}
extern "C" jlong Java_java_util_zip_Inflater_createStream(JNIEnv* env, jobject, jboolean noHeader) {
    UniquePtr<NativeZipStream> jstream(new NativeZipStream);
    if (jstream.get() == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
        return -1;
    }
    jstream->stream.adler = 1;

    /*
     * See zlib.h for documentation of the inflateInit2 windowBits parameter.
     *
     * zconf.h says the "requirements for inflate are (in bytes) 1 << windowBits
     * that is, 32K for windowBits=15 (default value) plus a few kilobytes
     * for small objects." This means that we can happily use the default
     * here without worrying about memory consumption.
     */
    int err = inflateInit2(&jstream->stream, noHeader ? -DEF_WBITS : DEF_WBITS);
    if (err != Z_OK) {
        throwExceptionForZlibError(env, "java/lang/IllegalArgumentException", err, jstream.get());
        return -1;
    }
    return reinterpret_cast<uintptr_t>(jstream.release());
}
/**
 * Clones an array of strings. Uses one contiguous block of memory so as to
 * maximize performance.
 *
 * @param address char** to clone
 * @param count number of attributes
 */
static jlong ExpatParser_cloneAttributes(JNIEnv* env, jobject, jlong address, jint count) {
    const char** source = reinterpret_cast<const char**>(static_cast<uintptr_t>(address));
    count *= 2;

    // Figure out how big the buffer needs to be.
    int arraySize = (count + 1) * sizeof(char*);
    int totalSize = arraySize;
    int stringLengths[count];
    for (int i = 0; i < count; i++) {
        int length = strlen(source[i]);
        stringLengths[i] = length;
        totalSize += length + 1;
    }

    char* buffer = new char[totalSize];
    if (buffer == NULL) {
        jniThrowOutOfMemoryError(env, NULL);
        return 0;
    }

    // Array is at the beginning of the buffer.
    char** clonedArray = reinterpret_cast<char**>(buffer);
    clonedArray[count] = NULL; // null terminate

    // String data follows immediately after.
    char* destinationString = buffer + arraySize;
    for (int i = 0; i < count; i++) {
        const char* sourceString = source[i];
        int stringLength = stringLengths[i];
        memcpy(destinationString, sourceString, stringLength + 1);
        clonedArray[i] = destinationString;
        destinationString += stringLength + 1;
    }

    return reinterpret_cast<uintptr_t>(buffer);
}