/*
 * Copyright (C) 2008 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * Set up values for System.getProperties().
 */
#include "Dalvik.h"

#include <stdlib.h>
#include <sys/utsname.h>
#include <limits.h>
#include <unistd.h>

/*
 * Create some storage for properties read from the command line.
 */
bool dvmPropertiesStartup(int maxProps)
{
    gDvm.maxProps = maxProps;
    if (maxProps > 0) {
        gDvm.propList = (char**) malloc(maxProps * sizeof(char*));
        if (gDvm.propList == NULL)
            return false;
    }
    gDvm.numProps = 0;

    return true;
}

/*
 * Clean up.
 */
void dvmPropertiesShutdown(void)
{
    int i;

    for (i = 0; i < gDvm.numProps; i++)
        free(gDvm.propList[i]);
    free(gDvm.propList);
    gDvm.propList = NULL;
}

/*
 * Add a property specified on the command line.  "argStr" has the form
 * "name=value".  "name" must have nonzero length.
 *
 * Returns "true" if argStr appears valid.
 */
bool dvmAddCommandLineProperty(const char* argStr)
{
    char* mangle;
    char* equals;

    mangle = strdup(argStr);
    equals = strchr(mangle, '=');
    if (equals == NULL || equals == mangle) {
        free(mangle);
        return false;
    }
    *equals = '\0';

    assert(gDvm.numProps < gDvm.maxProps);
    gDvm.propList[gDvm.numProps++] = mangle;

    return true;
}


/*
 * Find the "put" method for this class.
 *
 * Returns NULL and throws an exception if not found.
 */
static Method* getPut(ClassObject* clazz)
{
    Method* put;

    put = dvmFindVirtualMethodHierByDescriptor(clazz, "setProperty",
            "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;");
    if (put == NULL) {
        dvmThrowException("Ljava/lang/RuntimeException;",
            "could not find setProperty(String,String) in Properties");
        /* fall through to return */
    }
    return put;
}

/*
 * Set the value of the property.
 */
static void setProperty(Object* propObj, Method* put, const char* key,
    const char* value)
{
    StringObject* keyStr;
    StringObject* valueStr;

    if (value == NULL) {
        /* unclear what to do; probably want to create prop w/ empty string */
        value = "";
    }

    keyStr = dvmCreateStringFromCstr(key, ALLOC_DEFAULT);
    valueStr = dvmCreateStringFromCstr(value, ALLOC_DEFAULT);
    if (keyStr == NULL || valueStr == NULL) {
        LOGW("setProperty string creation failed\n");
        goto bail;
    }

    JValue unused;
    dvmCallMethod(dvmThreadSelf(), put, propObj, &unused, keyStr, valueStr);

bail:
    dvmReleaseTrackedAlloc((Object*) keyStr, NULL);
    dvmReleaseTrackedAlloc((Object*) valueStr, NULL);
}

/*
 * Create the VM-default system properties.
 *
 * We can do them here, or do them in interpreted code with lots of native
 * methods to get bits and pieces.  This is a bit smaller.
 */
void dvmCreateDefaultProperties(Object* propObj)
{
    Method* put = getPut(propObj->clazz);

    if (put == NULL)
        return;

    struct utsname info;
    uname(&info);

    /* constant strings that are used multiple times below */
    const char *projectUrl = "http://www.android.com/";
    const char *projectName = "The Android Project";

    /*
     * These are listed in the docs.
     */

    setProperty(propObj, put, "java.boot.class.path", gDvm.bootClassPathStr);
    setProperty(propObj, put, "java.class.path", gDvm.classPathStr);
    setProperty(propObj, put, "java.class.version", "46.0");
    setProperty(propObj, put, "java.compiler", "");
    setProperty(propObj, put, "java.ext.dirs", "");

    if (getenv("JAVA_HOME") != NULL) {
        setProperty(propObj, put, "java.home", getenv("JAVA_HOME"));
    } else {
        setProperty(propObj, put, "java.home", "/system");
    }

    setProperty(propObj, put, "java.io.tmpdir", "/tmp");
    setProperty(propObj, put, "java.library.path", getenv("LD_LIBRARY_PATH"));

    setProperty(propObj, put, "java.vendor", projectName);
    setProperty(propObj, put, "java.vendor.url", projectUrl);
    setProperty(propObj, put, "java.version", "0");
    setProperty(propObj, put, "java.vm.name", "Dalvik");
    setProperty(propObj, put, "java.vm.specification.name",
            "Dalvik Virtual Machine Specification");
    setProperty(propObj, put, "java.vm.specification.vendor", projectName);
    setProperty(propObj, put, "java.vm.specification.version", "0.9");
    setProperty(propObj, put, "java.vm.vendor", projectName);

    char tmpBuf[64];
    sprintf(tmpBuf, "%d.%d.%d",
        DALVIK_MAJOR_VERSION, DALVIK_MINOR_VERSION, DALVIK_BUG_VERSION);
    setProperty(propObj, put, "java.vm.version", tmpBuf);

    setProperty(propObj, put, "java.specification.name",
            "Dalvik Core Library");
    setProperty(propObj, put, "java.specification.vendor", projectName);
    setProperty(propObj, put, "java.specification.version", "0.9");

    #define OS_ARCH generic /* TODO: Use an "arch" header. */
    #define OS_ARCH_QUOTE(x) #x
    setProperty(propObj, put, "os.arch", OS_ARCH_QUOTE(OS_ARCH));
    #undef OS_ARCH
    #undef OS_ARCH_QUOTE

    setProperty(propObj, put, "os.name", info.sysname);
    setProperty(propObj, put, "os.version", info.release);
    setProperty(propObj, put, "user.home", getenv("HOME"));
    setProperty(propObj, put, "user.name", getenv("USER"));

    char path[PATH_MAX];
    setProperty(propObj, put, "user.dir", getcwd(path, sizeof(path)));

    setProperty(propObj, put, "file.separator", "/");
    setProperty(propObj, put, "line.separator", "\n");
    setProperty(propObj, put, "path.separator", ":");
    
    /*
     * These show up elsewhere, so do them here too.
     */
    setProperty(propObj, put, "java.runtime.name", "Android Runtime");
    setProperty(propObj, put, "java.runtime.version", "0.9");
    setProperty(propObj, put, "java.vm.vendor.url", projectUrl);

    setProperty(propObj, put, "file.encoding", "UTF-8");
    setProperty(propObj, put, "user.language", "en");
    setProperty(propObj, put, "user.region", "US");

    /*
     * These are unique to Android/Dalvik.
     */
    setProperty(propObj, put, "android.vm.dexfile", "true");
}

/*
 * Add anything specified on the command line.
 */
void dvmSetCommandLineProperties(Object* propObj)
{
    Method* put = getPut(propObj->clazz);
    int i;

    if (put == NULL)
        return;

    for (i = 0; i < gDvm.numProps; i++) {
        const char* value;

        /* value starts after the end of the key string */
        for (value = gDvm.propList[i]; *value != '\0'; value++)
            ;
        setProperty(propObj, put, gDvm.propList[i], value+1);
    }
}

/*
 * Get a property by calling System.getProperty(key).
 *
 * Returns a newly-allocated string, or NULL on failure or key not found.
 * (Unexpected failures will also raise an exception.)
 */
char* dvmGetProperty(const char* key)
{
    ClassObject* system;
    Method* getProp;
    StringObject* keyObj = NULL;
    StringObject* valueObj;
    char* result = NULL;

    assert(key != NULL);

    system = dvmFindSystemClass("Ljava/lang/System;");
    if (system == NULL)
        goto bail;

    getProp = dvmFindDirectMethodByDescriptor(system, "getProperty",
        "(Ljava/lang/String;)Ljava/lang/String;");
    if (getProp == NULL) {
        LOGW("Could not find getProperty(String) in java.lang.System\n");
        goto bail;
    }

    keyObj = dvmCreateStringFromCstr(key, ALLOC_DEFAULT);
    if (keyObj == NULL)
        goto bail;

    JValue val;
    dvmCallMethod(dvmThreadSelf(), getProp, NULL, &val, keyObj);
    valueObj = (StringObject*) val.l;
    if (valueObj == NULL)
        goto bail;

    result = dvmCreateCstrFromString(valueObj);
    /* fall through with result */

bail:
    dvmReleaseTrackedAlloc((Object*)keyObj, NULL);
    return result;
}

