com.github.wshackle.java4cpp.J4CppMain.java Source code

Java tutorial

Introduction

Here is the source code for com.github.wshackle.java4cpp.J4CppMain.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package com.github.wshackle.java4cpp;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.Stack;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

/**
 *
 * @author Will Shackleford {@literal <william.shackleford@nist.gov>}
 */
public class J4CppMain {

    private static String getCppPrimitiveType(Class<?> clss) {
        if (boolean.class.isAssignableFrom(clss)) {
            return "jboolean";
        } else if (byte.class.isAssignableFrom(clss)) {
            return "jbyte";
        } else if (char.class.isAssignableFrom(clss)) {
            return "jchar";
        } else if (short.class.isAssignableFrom(clss)) {
            return "jshort";
        } else if (int.class.isAssignableFrom(clss)) {
            return "jint";
        } else if (long.class.isAssignableFrom(clss)) {
            return "jlong";
        } else if (float.class.isAssignableFrom(clss)) {
            return "jfloat";
        } else if (double.class.isAssignableFrom(clss)) {
            return "jdouble";
        } else if (void.class.isAssignableFrom(clss)) {
            return "void";
        } else {
            throw new IllegalArgumentException("getCppPrimitiveType() Class=" + clss + " not recognized.");
        }
    }

    private static String getCppArrayType(Class<?> clss) {
        if (boolean.class.isAssignableFrom(clss)) {
            return "jbooleanArray";
        } else if (byte.class.isAssignableFrom(clss)) {
            return "jbyteArray";
        } else if (char.class.isAssignableFrom(clss)) {
            return "jcharArray";
        } else if (short.class.isAssignableFrom(clss)) {
            return "jshortArray";
        } else if (int.class.isAssignableFrom(clss)) {
            return "jintArray";
        } else if (long.class.isAssignableFrom(clss)) {
            return "jlongArray";
        } else if (float.class.isAssignableFrom(clss)) {
            return "jfloatArray";
        } else if (double.class.isAssignableFrom(clss)) {
            return "jdoubleArray";
        } else {
            return "jobjectArray";
        }
    }

    private static String getCppEasyCallArrayType(Class<?> clss) {
        if (boolean.class.isAssignableFrom(clss)) {
            return "jboolean *";
        } else if (byte.class.isAssignableFrom(clss)) {
            return "jbyte *";
        } else if (char.class.isAssignableFrom(clss)) {
            return "jchar *";
        } else if (short.class.isAssignableFrom(clss)) {
            return "jshort *";
        } else if (int.class.isAssignableFrom(clss)) {
            return "jint *";
        } else if (long.class.isAssignableFrom(clss)) {
            return "jlong *";
        } else if (float.class.isAssignableFrom(clss)) {
            return "jfloat *";
        } else if (double.class.isAssignableFrom(clss)) {
            return "jdouble *";
        } else {
            return "jobject *";
        }
        //        ClassLoader.getSystemClassLoader();
    }

    public static String namespace = "JavaForCpp";

    private static String getModifiedClassName(Class cls) {
        Class enclosingClass = cls.getEnclosingClass();
        if (null != enclosingClass) {
            String en = enclosingClass.getCanonicalName();
            return en + cls.getCanonicalName().substring(en.length() + 1);
        }
        return cls.getCanonicalName();
    }

    private static String getCppRelativeName(Class<?> cls, Class<?> relClass) {
        String basepackages[] = getModifiedClassName(cls).split("\\.");
        String packages[] = relClass.getCanonicalName().split("\\.");
        int i = 0;
        int index = 0;
        boolean match = true;
        for (i = 0; i < basepackages.length - 1 && i < packages.length - 1; i++) {
            if (!packages[i].equals(basepackages[i])) {
                match = false;
                break;
            }
            index += packages[i].length() + 1;
        }
        if (match) {
            return getModifiedClassName(cls).substring(index).replace(".", "::");
        }

        return "::" + namespace + "::" + getModifiedClassName(cls).replace(".", "::");
    }

    private static String getCppType(Class<?> clss, Class<?> relClass) {
        if (clss.isArray()) {
            Class<?> componentType = clss.getComponentType();
            return getCppArrayType(componentType);
        }
        if (clss.isPrimitive()) {
            return getCppPrimitiveType(clss);
        }
        if (Void.class.isAssignableFrom(clss)) {
            return "void";
        } else if (isString(clss)) {
            return "jstring";
        } else {
            return getCppRelativeName(clss, relClass);
        }
    }

    private static String getEasyCallCppType(Class<?> clss, Class<?> relClass) {
        if (clss.isArray()) {
            Class<?> componentType = clss.getComponentType();
            return getCppEasyCallArrayType(componentType);
        }
        if (clss.isPrimitive()) {
            return getCppPrimitiveType(clss);
        }
        if (Void.class.isAssignableFrom(clss)) {
            return "void";
        } else if (isString(clss)) {
            return "const char *";
        } else {
            return getCppRelativeName(clss, relClass);
        }
    }

    private static String getCppModifiers(int modifiers) {
        String out = "";
        if (Modifier.isStatic(modifiers)) {
            out += "static ";
        }
        return out;
    }

    private static String addConstRefIndicator(Class c, String orig) {
        if (!c.isPrimitive() && !isString(c) && !c.isArray()) {
            return "const " + orig + " &";
        }
        return orig;
    }

    private static String classToParamNameDecl(Class<?> c, int index) {
        if (c.isArray()) {
            return classToParamName(c.getComponentType()) + "Array_" + index;
        }
        return c.getSimpleName().substring(0, 1).toLowerCase() + c.getSimpleName().substring(1) + "_" + index;
    }

    private static String addCppJThis(Class c) {
        if (c.isPrimitive() || c.isArray() || isString(c)) {
            return "";
        } else {
            return ".jthis";
        }
    }

    private static String classToParamName(Class<?> c) {
        if (c.isArray()) {
            return classToParamName(c.getComponentType()) + "Array";
        }
        return c.getSimpleName().substring(0, 1).toLowerCase() + c.getSimpleName().substring(1);
    }

    private static String classToJNISignature(Class<?> clss) {
        if (boolean.class.isAssignableFrom(clss)) {
            return "Z";
        } else if (byte.class.isAssignableFrom(clss)) {
            return "B";
        } else if (char.class.isAssignableFrom(clss)) {
            return "C";
        } else if (short.class.isAssignableFrom(clss)) {
            return "S";
        } else if (int.class.isAssignableFrom(clss)) {
            return "I";
        } else if (long.class.isAssignableFrom(clss)) {
            return "J";
        } else if (float.class.isAssignableFrom(clss)) {
            return "F";
        } else if (double.class.isAssignableFrom(clss)) {
            return "D";
        } else if (void.class.isAssignableFrom(clss)) {
            return "V";
        } else if (clss.isArray()) {
            return "[" + classToJNISignature(clss.getComponentType());
        } else {
            if (null != clss.getEnclosingClass()) {
                String s = classToJNISignature(clss.getEnclosingClass());
                String s1 = clss.getName();
                int dollarIndex = s1.indexOf('$');
                return s.substring(0, s.length() - 1) + s1.substring(dollarIndex) + ";";
            }
            return "L" + clss.getCanonicalName().replace(".", "/") + ";";
        }

    }

    private static String getJNIParamSignature(Class<?>[] clsses) {
        String s = "";
        for (int i = 0; i < clsses.length; i++) {
            Class<?> clsse = clsses[i];
            s += classToJNISignature(clsse);
        }
        return s;
    }

    private static String getCppParamNames(Class<?>[] clsses) {
        //        return Stream.of(clsses)
        //                .map(J4CppMain::classToParamName)
        //                .collect(Collectors.joining(","));
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < clsses.length; i++) {
            Class<?> clsse = clsses[i];
            String name = getParamName(clsse, i);
            sb.append(name);
            if (i < clsses.length - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

    private static String getCppParamNamesIn(Class<?>[] clsses) {
        //        return Stream.of(clsses)
        //                .map(J4CppMain::classToParamName)
        //                .collect(Collectors.joining(","));
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < clsses.length; i++) {
            Class<?> clsse = clsses[i];
            String name = getParamNameIn(clsse, i);
            sb.append(name);
            if (i < clsses.length - 1) {
                sb.append(",");
            }
        }
        return sb.toString();
    }

    private static String getParamName(Class<?> clsse, int i) {
        return classToParamName(clsse) + "_" + i + addCppJThis(clsse);
    }

    private static String getParamNameIn(Class<?> clsse, int i) {
        return classToParamName(clsse) + "_" + i;
    }

    private static String getCppParamDeclarations(Class<?>[] clsses, Class<?> relClass) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < clsses.length; i++) {
            Class<?> clsse = clsses[i];
            String name = addConstRefIndicator(clsse, getCppType(clsse, relClass)) + " "
                    + classToParamNameDecl(clsse, i);
            sb.append(name);
            if (i < clsses.length - 1) {
                sb.append(",");
            }
        }
        sb.append(")");
        return sb.toString();
        //        return "("
        //                + Stream.of(clsses)
        //                .map(c -> getCppType(c) + " " + classToParamNameDecl(c))
        //                .collect(Collectors.joining(",")) + ")";
    }

    private static String getEasyCallCppParamDeclarations(Class<?>[] clsses, Class<?> relClass) {
        StringBuilder sb = new StringBuilder();
        sb.append("(");
        for (int i = 0; i < clsses.length; i++) {
            Class<?> clsse = clsses[i];
            String type = getEasyCallCppType(clsse, relClass);
            sb.append(type);
            sb.append(' ');
            String name = "easyArg_" + i;// classToParamNameDecl(clsse, i);
            sb.append(name);
            if (i < clsses.length - 1 || clsse.isArray()) {
                sb.append(',');
            }
            if (clsse.isArray()) {
                sb.append("jsize ");
                sb.append(name);
                sb.append("_length");
                if (i < clsses.length - 1) {
                    sb.append(',');
                }
            }
        }
        sb.append(")");
        return sb.toString();
        //        return "("
        //                + Stream.of(clsses)
        //                .map(c -> getCppType(c) + " " + classToParamNameDecl(c))
        //                .collect(Collectors.joining(",")) + ")";
    }

    private static Set<String> badNames = getBadNames();

    private static Set<String> getBadNames() {
        Set<String> badNamesSet = new HashSet<>();
        badNamesSet.addAll(Arrays.asList("and", "and_eq", "bitand", "bitor", "compl", "not", "not_eq", "or",
                "not_eq", "or", "or_eq", "xor", "xor_eq", "delete", "namespace", "union", "cast"));
        return badNamesSet;
    }

    private static boolean fixMultipleOverrides = Boolean.getBoolean("fixMultipleOverrides");

    private static String fixMethodName(Method m) {
        String mname = m.getName();

        if (fixMultipleOverrides) {
            Method ma[] = m.getDeclaringClass().getMethods();
            int index = 0;
            boolean index_incremented = false;
            boolean has_match = false;
            Arrays.sort(ma, new Comparator<Method>() {
                @Override
                public int compare(Method o1, Method o2) {
                    return Arrays.toString(o1.getParameterTypes())
                            .compareTo(Arrays.toString(o2.getParameterTypes()));
                }
            });
            List<Method> matchingMethods = new ArrayList<>();
            for (int i = 0; i < ma.length; i++) {
                Method method = ma[i];
                if (method.getParameterTypes().length > 0 && m.getParameterTypes().length > 0
                        && m.getParameterTypes()[0].isPrimitive() != method.getParameterTypes()[0].isPrimitive()) {
                    continue;
                }
                if (!method.equals(m) && m.getName().equals(method.getName())
                        && m.getParameterTypes().length == method.getParameterTypes().length) {
                    has_match = true;
                    matchingMethods.add(method);
                }
            }
            for (int i = 0; i < ma.length; i++) {
                Method method = ma[i];
                if (method.equals(m)) {
                    break;
                }
                if (method.getParameterTypes().length != m.getParameterTypes().length) {
                    continue;
                }
                if (method.getParameterTypes().length > 0 && m.getParameterTypes().length > 0
                        && m.getParameterTypes()[0].isPrimitive() != method.getParameterTypes()[0].isPrimitive()) {
                    continue;
                }
                if (m.getParameterTypes().length >= 1) {
                    if (String.class.isAssignableFrom(m.getParameterTypes()[0]) != String.class
                            .isAssignableFrom(method.getParameterTypes()[0])) {
                        continue;
                    }
                }
                if (method.getName().equals(m.getName())) {
                    index++;
                    index_incremented = true;
                }
            }
            int start_index = 0;
            while (index_incremented) {
                index_incremented = false;
                for (int i = 0; i < ma.length; i++) {
                    Method method = ma[i];
                    for (int j = start_index; j <= index; j++) {
                        if (method.getName().equals(m.getName() + j)
                                && m.getParameterTypes().length == method.getParameterTypes().length) {
                            index++;
                            index_incremented = true;
                        }
                    }
                }
                start_index = index;
            }
            if (has_match) {
                String paramstring = "";
                Class[] paramclasses = m.getParameterTypes();
                for (int i = 0; i < paramclasses.length; i++) {
                    paramstring += classToMethodAppendage(paramclasses[i]);
                }
                mname = mname + paramstring;
                if (mname.contains(";")) {
                    System.out.println("paramclasses = " + paramclasses);
                }
            }
        }

        if (badNames.contains(mname)) {
            return mname + "Method";
        }
        return mname;
    }

    private static String classToMethodAppendage(Class clzz) {
        return clzz.getName().replace("Ljava.lang", "").replace("Ljava.io", "").replace("Ljava.util", "")
                .replace("Ljava.net", "").replace("Ljava.math", "").replace("java.lang", "").replace("java.io", "")
                .replace("java.util", "").replace("java.net", "").replace("java.math", "")
                .replaceAll("[.\\[\\]\\$_;]+", "");
    }

    private static String getCppDeclaration(Method m, Class<?> relClass) {
        return getCppModifiers(m.getModifiers()) + getCppType(m.getReturnType(), relClass) + " " + fixMethodName(m)
                + getCppParamDeclarations(m.getParameterTypes(), relClass) + ";";
    }

    private static String getNativeMethodCppDeclaration(Method m, Class<?> relClass) {
        return getCppModifiers(m.getModifiers()) + getCppType(m.getReturnType(), relClass) + " " + fixMethodName(m)
                + "Native" + getCppParamDeclarations(m.getParameterTypes(), relClass) + ";";
    }

    private static String getCppFieldGetterDeclaration(Field f, Class<?> relClass) {
        return getCppModifiers(f.getModifiers()) + getCppType(f.getType(), relClass) + " " + "get"
                + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1) + "();";
    }

    private static String getCppFieldSetterDeclaration(Field f, Class<?> relClass) {
        return getCppModifiers(f.getModifiers()) + "void set" + f.getName().substring(0, 1).toUpperCase()
                + f.getName().substring(1) + "("
                + addConstRefIndicator(f.getType(), getCppType(f.getType(), relClass)) + " "
                + classToParamNameDecl(f.getType(), 0) + ");";
    }

    private static String getCppFieldGetterDefinitionStart(String tabs, String clssOnlyName, Field f,
            Class<?> relClass) {
        return tabs //+ getCppModifiers(m)
                + getCppType(f.getType(), relClass) + " " + clssOnlyName + "::" + "get"
                + f.getName().substring(0, 1).toUpperCase() + f.getName().substring(1) + "() {";
    }

    private static String getCppFieldSetterDefinitionStart(String tabs, String clssOnlyName, Field f,
            Class<?> relClass) {
        return tabs //+ getCppModifiers(m)
                + "void " + clssOnlyName + "::" + "set" + f.getName().substring(0, 1).toUpperCase()
                + f.getName().substring(1) + "("
                + addConstRefIndicator(f.getType(), getCppType(f.getType(), relClass)) + " "
                + classToParamNameDecl(f.getType(), 0) + ") {";
    }

    private static String getCppMethodDefinitionStart(String tabs, String clssOnlyName, Method m,
            Class<?> relClass) {
        return tabs //+ getCppModifiers(m)
                + getCppType(m.getReturnType(), relClass) + " " + clssOnlyName + "::" + fixMethodName(m)
                + getCppParamDeclarations(m.getParameterTypes(), relClass) + " {";
    }

    private static String getEasyCallCppMethodDefinitionStart(String tabs, String clssOnlyName, Method m,
            Class<?> relClass) {
        return tabs //+ getCppModifiers(m)
                + getCppType(m.getReturnType(), relClass) + " " + clssOnlyName + "::" + fixMethodName(m)
                + getEasyCallCppParamDeclarations(m.getParameterTypes(), relClass) + " {";
    }

    public static String getOnFailString(Class returnClass, Class relClass) {
        if (boolean.class.isAssignableFrom(returnClass)) {
            return "return false;";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "return (jbyte) -1;";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "return (jchar) -1;";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "return (jshort) -1;";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "return (jint) -1;";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "return (jlong) -1;";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "return (jfloat) -1.0;";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "return (jdouble) -1.0;";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "return;";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "return;";
        } else if (isString(returnClass)) {
            return "return NULL;";
        } else if (returnClass.isArray()) {
            return "return NULL;";
        } else {
            return "static " + getCppRelativeName(returnClass, relClass)
                    + " nullObject((jobject)NULL,false); return nullObject;";
        }
    }

    private static boolean isString(Class returnClass) {
        return String.class.isAssignableFrom(returnClass);
    }

    private static boolean isPrimitiveArray(Class clss) {
        return clss.isArray() && clss.getComponentType().isPrimitive();
    }

    public static String getMethodReturnVarArrayType(Class returnClass) {
        if (boolean.class.isAssignableFrom(returnClass)) {
            return "jbooleanArray";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyteArray";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jcharArray";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshortArray";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jintArray";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlongArray";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloatArray";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdoubleArray";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jobjectArray";
        } else {
            return "jobjectArray";
        }
    }

    public static String getMethodReturnVarType(Class returnClass) {
        if (returnClass.isArray()) {
            return getMethodReturnVarArrayType(returnClass.getComponentType());
        } else if (boolean.class.isAssignableFrom(returnClass)) {
            return "jboolean";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyte";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jchar";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshort";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jint";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlong";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloat";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdouble";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jstring";
        } else {
            return "jobject";
        }
    }

    public static String getMethodReturnOutVarType(Class returnClass, Class relClass) {
        if (returnClass.isArray()) {
            return getMethodReturnVarArrayType(returnClass.getComponentType());
        } else if (boolean.class.isAssignableFrom(returnClass)) {
            return "jboolean";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyte";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jchar";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshort";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jint";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlong";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloat";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdouble";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jstring";
        } else {
            return getCppType(returnClass, relClass);
        }
    }

    public static String getMethodReturnArrayVarDeclare(Class returnClass) {
        if (boolean.class.isAssignableFrom(returnClass)) {
            return "jbooleanArray retVal=NULL;";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyteArray retVal=NULL;";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jcharArray retVal=NULL;";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshortArray retVal=NULL;";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jintArray retVal=NULL;";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlongArray retVal=NULL;";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloatArray retVal=NULL;";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdoubleArray retVal=NULL;";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jobjectArray retVal=NULL;";
        } else {
            return "jobjectArray retVal=NULL;";
        }
    }

    public static String getMethodReturnVarDeclare(Class returnClass) {
        if (returnClass.isArray()) {
            return getMethodReturnArrayVarDeclare(returnClass.getComponentType());
        } else if (boolean.class.isAssignableFrom(returnClass)) {
            return "jboolean retVal=false;";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyte retVal= (jbyte) -1;";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jchar retVal= (jchar) -1;";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshort retVal=(jshort) -1;";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jint retVal= (jint) -1;";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlong retVal= (jlong) -1;";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloat retVal= (jfloat) -1.0;";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdouble retVal= (jdouble) -1.0;";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jstring retVal=NULL;";
        } else {
            return "jobject retVal=NULL;";
        }
    }

    public static String getMethodReturnVarDeclareOut(Class returnClass, Class relClass) {
        if (returnClass.isArray()) {
            return getMethodReturnArrayVarDeclare(returnClass.getComponentType());
        } else if (boolean.class.isAssignableFrom(returnClass)) {
            return "jboolean retVal=false;";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "jbyte retVal= (jbyte) -1;";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "jchar retVal= (jchar) -1;";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "jshort retVal=(jshort) -1;";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "jint retVal= (jint) -1;";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "jlong retVal= (jlong) -1;";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "jfloat retVal= (jfloat) -1.0;";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "jdouble retVal= (jdouble) -1.0;";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (Void.class.isAssignableFrom(returnClass)) {
            return "";
        } else if (isString(returnClass)) {
            return "jstring retVal=NULL;";
        } else {
            return getCppType(returnClass, relClass) + " retVal((jobject)NULL,false);";
        }
    }

    public static String getMethodCallString(Class returnClass) {
        if (boolean.class.isAssignableFrom(returnClass)) {
            return "Boolean";
        } else if (byte.class.isAssignableFrom(returnClass)) {
            return "Byte";
        } else if (char.class.isAssignableFrom(returnClass)) {
            return "Char";
        } else if (short.class.isAssignableFrom(returnClass)) {
            return "Short";
        } else if (int.class.isAssignableFrom(returnClass)) {
            return "Int";
        } else if (long.class.isAssignableFrom(returnClass)) {
            return "Long";
        } else if (float.class.isAssignableFrom(returnClass)) {
            return "Float";
        } else if (double.class.isAssignableFrom(returnClass)) {
            return "Double";
        } else if (void.class.isAssignableFrom(returnClass)) {
            return "Void";
        } else {
            return "Object";
        }
    }

    //    public static Optional<String> getArg(final String param, final String args[]) {
    //        Arrays.stream(args)
    //    $(VC_IncludePath)
    //$(WindowsSDK_IncludePath)
    //                .
    //    }
    private static String replaceVars(Map<String, String> map, final String orig_str) {
        String key = null;
        String val = null;
        String str = orig_str;
        try {
            for (Map.Entry<String, String> e : map.entrySet()) {
                key = e.getKey();
                val = e.getValue();
                if (null == val) {
                    val = "";
                }
                str = str.replace(key, val);
            }
        } catch (Throwable t) {
            System.err.println("orig_str=" + orig_str);
            System.err.println("str=" + str);
            System.err.println("key=" + key);
            System.err.println("val=" + val);
            System.err.println("map=" + map);
            t.printStackTrace();
            throw new RuntimeException(t);
        }
        return str;
    }

    private static boolean isVoid(Class<?> clss) {
        return clss.equals(Void.class) || clss.equals(void.class);
    }

    private static boolean isArrayStringMethod(Method m) {
        Class paramClasses[] = m.getParameterTypes();
        return paramClasses.length == 1 && paramClasses[0].isArray()
                && paramClasses[0].getComponentType().equals(String.class);
    }

    private static boolean checkClass(Class<?> clss, List<Class> classes) {
        Class<?> componentClass = clss.getComponentType();
        boolean ret = isString(clss)
                || clss.equals(Object.class) || clss.isPrimitive() || (clss.isArray() && null != componentClass
                        && !componentClass.isArray() && checkClass(componentClass, classes))
                || classes.contains(clss);
        if (!ret) {
            if (verbose) {
                if (clss.isArray()) {
                    System.out.println("checkClass skipping " + clss + " component " + clss.getComponentType());
                } else {
                    System.out.println("checkClass skipping " + clss);
                }
            }
        }
        return ret;
    }

    private static boolean checkParameters(Class paramTypes[], List<Class> classes) {
        for (Class paramType : paramTypes) {
            if (!checkClass(paramType, classes)) {
                return false;
            }
        }
        return true;
    }

    private static boolean checkMethod(Method m, List<Class> classes) {
        if (m.getDeclaringClass().getName().equals(m.getName())) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " method same as classname.");
            }
            return false;
        }
        if (m.getDeclaringClass().getName().endsWith("." + m.getName())) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " method same as classname.");
            }
            return false;
        }
        if (m.isSynthetic()) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " isSynthetic.");
            }
            return false;
        }
        if (!Modifier.isPublic(m.getModifiers())) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " not public");
            }
            return false;
        }
        if (!checkClass(m.getReturnType(), classes)) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " return type not in classes list.");
            }
            return false;
        }
        boolean ret = checkParameters(m.getParameterTypes(), classes);
        if (!ret) {
            if (verbose) {
                System.out.println("checkMethod skipping " + m + " a parameter type is not in classes list");
            }
        }
        return ret;
    }

    public static boolean isAddableClass(Class<?> clss, Set<Class> excludedClasses) {
        if (clss.isArray() || clss.isSynthetic() || clss.isAnnotation() || clss.isPrimitive()) {
            return false;
        }
        //        if(clss.getCanonicalName().contains("Dialog") || clss.getName().contains("ModalExlusionType")) {
        //            if(verbose) System.out.println("clss = " + clss);
        //        }
        //        if (clss.getEnclosingClass() != null) {
        //            return false;
        //        }
        String canonicalName = null;
        try {
            canonicalName = clss.getCanonicalName();
        } catch (Throwable t) {
            // leaving canonicalName null is enough
        }
        if (null == canonicalName) {
            return false;
        }
        if (canonicalName.indexOf('$') >= 0) {
            return false;
        }
        String pkgNames[] = clss.getCanonicalName().split("\\.");
        for (int i = 0; i < pkgNames.length; i++) {
            String pkgName = pkgNames[i];
            if (badNames.contains(pkgName)) {
                return false;
            }
        }
        Method ma[] = null;
        try {
            ma = clss.getDeclaredMethods();
        } catch (Throwable t) {
            // leaving canonicalName null is enough
        }
        if (null == ma) {
            return false;
        }
        return !excludedClasses.contains(clss);
    }

    private static boolean isMethodToMakeEasy(Method m) {
        if (Modifier.isStatic(m.getModifiers())) {
            return false;
        }
        Class<?> types[] = m.getParameterTypes();
        for (int i = 0; i < types.length; i++) {
            Class<?> type = types[i];
            if (type.isArray() && !type.getComponentType().isPrimitive()) {
                return false;
            }
        }
        for (int i = 0; i < types.length; i++) {
            Class<?> type = types[i];
            if (type.isArray() || isString(type)) {
                return true;
            }
        }
        return false;
        //        return !Modifier.isStatic(m.getModifiers())
        //                && Arrays.stream(m.getParameterTypes())
        //                .anyMatch(t -> t.isArray() || isString(t))
        //                && Arrays.stream(m.getParameterTypes())
        //                .noneMatch(t -> t.isArray() && !t.getComponentType().isPrimitive());
    }

    private static boolean isConstructorToMakeEasy(Constructor c, Class relClss) {
        Class<?> types[] = c.getParameterTypes();
        for (int i = 0; i < types.length; i++) {
            Class<?> type = types[i];
            if (type.isArray() && !type.getComponentType().isPrimitive()) {
                return false;
            }
        }
        for (int i = 0; i < types.length; i++) {
            Class<?> type = types[i];
            if (type.isArray() || isString(type)) {
                return true;
            }
        }
        return false;
        //        return Arrays.stream(c.getParameterTypes())
        //                .anyMatch(t -> t.isArray() || isString(t))
        //                && Arrays.stream(c.getParameterTypes())
        //                .noneMatch(t -> t.isArray() && !t.getComponentType().isPrimitive());
    }

    private static String getCppClassName(Class clss) {
        String clssName = clss.getCanonicalName();
        String pkgs[] = clssName.split("\\.");
        String clssOnlyName = pkgs[pkgs.length - 1];
        Class enclosingClass = clss.getEnclosingClass();
        if (enclosingClass != null) {
            String enclosingClassName = enclosingClass.getCanonicalName();
            String enclosingPkgs[] = enclosingClassName.split("\\.");
            String enclosingName = enclosingPkgs[enclosingPkgs.length - 1];
            clssOnlyName = enclosingName + clssOnlyName;
        }
        return clssOnlyName;
    }

    private static String getEasyCallCppDeclaration(Method m, Class<?> relClass) {
        return getCppModifiers(m.getModifiers()) + getCppType(m.getReturnType(), relClass) + " " + fixMethodName(m)
                + getEasyCallCppParamDeclarations(m.getParameterTypes(), relClass) + ";";
    }

    public static boolean hasNoArgConstructor(Constructor[] constructors) {
        for (Constructor c : constructors) {
            if ((Modifier.isProtected(c.getModifiers()) || Modifier.isPublic(c.getModifiers()))
                    && c.getParameterTypes().length == 0) {
                return true;
            }
        }
        return false;
    }

    private static boolean addGetterMethod(Field f, Class clss, List<Class> classes) {
        if (!Modifier.isPublic(f.getModifiers())) {
            return false;
        }
        if (!f.getType().isPrimitive() && !String.class.equals(f.getType()) && !classes.contains(f.getType())) {
            return false;
        }
        Method ma[] = clss.getMethods();
        for (int i = 0; i < ma.length; i++) {
            Method method = ma[i];
            if (method.getName().equalsIgnoreCase(f.getName())) {
                return false;
            }
            if (method.getName().equalsIgnoreCase("get" + f.getName())) {
                return false;
            }
            if (method.getName().equalsIgnoreCase("set" + f.getName())) {
                return false;
            }
        }
        return true;
    }

    private static boolean addSetterMethod(Field f, Class clss, List<Class> classes) {
        if (!Modifier.isPublic(f.getModifiers())) {
            return false;
        }
        if (!f.getType().isPrimitive() && !String.class.equals(f.getType()) && !classes.contains(f.getType())) {
            return false;
        }
        if (Modifier.isFinal(f.getModifiers())) {
            return false;
        }
        Method ma[] = clss.getMethods();
        for (int i = 0; i < ma.length; i++) {
            Method method = ma[i];
            if (method.getName().equalsIgnoreCase(f.getName())) {
                return false;
            }
            if (method.getName().equalsIgnoreCase("get" + f.getName())) {
                return false;
            }
            if (method.getName().equalsIgnoreCase("set" + f.getName())) {
                return false;
            }
        }
        return true;
    }

    public static String getHomeDir() throws IOException {
        String userHomeProp = System.getProperty("user.home");
        if (verbose) {
            System.out.println("userDirProp = " + userHomeProp);
        }
        String homeDir = new File(userHomeProp).getCanonicalPath();
        if (verbose) {
            System.out.println("homeDir = " + homeDir);
        }
        return homeDir;
    }

    public static String getCurrentDir() throws IOException {
        String userDirProp = System.getProperty("user.dir");
        if (verbose) {
            System.out.println("userDirProp = " + userDirProp);
        }
        String currentDir = new File(userDirProp).getCanonicalPath();
        if (verbose) {
            System.out.println("currentDir = " + currentDir);
        }
        return currentDir.replace("\\", "\\\\");
    }

    public static boolean verbose = false;

    public static boolean main_completed = false;

    public static void main(String[] args) throws IOException, ClassNotFoundException {
        main_completed = false;

        Options options = new Options();
        options.addOption(Option.builder("?").desc("Print this message").longOpt("help").build());
        options.addOption(Option.builder("n").hasArg().desc("C++ namespace for newly generated classes.")
                .longOpt("namespace").build());
        options.addOption(
                Option.builder("c").hasArgs().desc("Single Java class to extract.").longOpt("classes").build());
        options.addOption(
                Option.builder("p").hasArgs().desc("Java Package prefix to extract").longOpt("packages").build());
        options.addOption(Option.builder("o").hasArg().desc("Output C++ source file.").longOpt("output").build());
        options.addOption(Option.builder("j").hasArg().desc("Input jar file").longOpt("jar").build());
        options.addOption(Option.builder("h").hasArg().desc("Output C++ header file.").longOpt("header").build());
        options.addOption(Option.builder("l").hasArg()
                .desc("Maximum limit on classes to extract from jars.[default=200]").longOpt("limit").build());
        options.addOption(Option.builder("v").desc("enable verbose output").longOpt("verbose").build());
        options.addOption(Option.builder().hasArg().desc("Classes per output file.[default=10]")
                .longOpt(CLASSESPEROUTPUT).build());
        options.addOption(Option.builder().hasArgs().desc(
                "Comma seperated list of nativeclass=javaclass native where nativeclass will be generated as an extension/implementation of the java class.")
                .longOpt("natives").build());
        options.addOption(Option.builder().hasArg()
                .desc("library name for System.loadLibrary(...) for native extension classes")
                .longOpt("loadlibname").build());
        String output = null;
        String header = null;
        String jar = null;
        Set<String> classnamesToFind = null;
        Set<String> packageprefixes = null;
        String loadlibname = null;

        Map<String, String> nativesNameMap = null;
        Map<String, Class> nativesClassMap = null;
        int limit = DEFAULT_LIMIT;
        int classes_per_file = 10;
        List<Class> classes = new ArrayList<>();

        String limitstring = Integer.toString(limit);

        try {
            // parse the command line arguments
            System.out.println("args = " + Arrays.toString(args));
            CommandLine line = new DefaultParser().parse(options, args);
            loadlibname = line.getOptionValue("loadlibname");
            verbose = line.hasOption("verbose");
            if (line.hasOption(CLASSESPEROUTPUT)) {
                String cpoStr = line.getOptionValue(CLASSESPEROUTPUT);
                try {
                    int cpoI = Integer.valueOf(cpoStr);
                    classes_per_file = cpoI;
                } catch (Exception e) {
                    System.err.println("Option for " + CLASSESPEROUTPUT + "=\"" + cpoStr + "\"");
                    printHelpAndExit(options, args);
                }
            }

            if (line.hasOption("natives")) {
                String natives[] = line.getOptionValues("natives");
                if (verbose) {
                    System.out.println("natives = " + Arrays.toString(natives));
                }
                nativesNameMap = new HashMap<>();
                nativesClassMap = new HashMap<>();
                for (int i = 0; i < natives.length; i++) {
                    int eq_index = natives[i].indexOf('=');
                    String nativeClassName = natives[i].substring(0, eq_index).trim();
                    String javaClassName = natives[i].substring(eq_index + 1).trim();
                    Class javaClass = null;
                    try {
                        javaClass = Class.forName(javaClassName);
                    } catch (ClassNotFoundException e) {
                        //e.printStackTrace();
                        System.err.println("Class for " + javaClassName
                                + " not found. (It may be found later in jar if specified.)");
                    }
                    nativesNameMap.put(javaClassName, nativeClassName);
                    if (javaClass != null) {
                        nativesClassMap.put(nativeClassName, javaClass);
                        if (!classes.contains(javaClass)) {
                            classes.add(javaClass);
                        }
                    }
                }
            }
            //            // validate that block-size has been set
            //            if (line.hasOption("block-size")) {
            //                // print the value of block-size
            //                if(verbose) System.out.println(line.getOptionValue("block-size"));
            //            }
            jar = line.getOptionValue("jar", jar);
            if (verbose) {
                System.out.println("jar = " + jar);
            }
            if (null != jar) {
                if (jar.startsWith("~/")) {
                    jar = new File(new File(getHomeDir()), jar.substring(2)).getCanonicalPath();
                }
                if (jar.startsWith("./")) {
                    jar = new File(new File(getCurrentDir()), jar.substring(2)).getCanonicalPath();
                }
                if (jar.startsWith("../")) {
                    jar = new File(new File(getCurrentDir()).getParentFile(), jar.substring(3)).getCanonicalPath();
                }
            }
            if (line.hasOption("classes")) {
                classnamesToFind = new HashSet<String>();
                String classStrings[] = line.getOptionValues("classes");
                if (verbose) {
                    System.out.println("classStrings = " + Arrays.toString(classStrings));
                }
                classnamesToFind.addAll(Arrays.asList(classStrings));
                if (verbose) {
                    System.out.println("classnamesToFind = " + classnamesToFind);
                }
            }
            //                if (!line.hasOption("namespace")) {
            //                    if (classname != null && classname.length() > 0) {
            //                        namespace = classname.toLowerCase().replace(".", "_");
            //                    } else if (jar != null && jar.length() > 0) {
            //                        int lastSep = jar.lastIndexOf(File.separator);
            //                        int start = Math.max(0, lastSep + 1);
            //                        int period = jar.indexOf('.', start + 1);
            //                        int end = Math.max(start + 1, period);
            //                        namespace = jar.substring(start, end).toLowerCase();
            //                        namespace = namespace.replace(" ", "_");
            //                        if (namespace.indexOf("-") > 0) {
            //                            namespace = namespace.substring(0, namespace.indexOf("-"));
            //                        }
            //                    }
            //                }

            namespace = line.getOptionValue("namespace", namespace);
            if (verbose) {
                System.out.println("namespace = " + namespace);
            }
            if (null != namespace) {
                output = namespace + ".cpp";
            }
            output = line.getOptionValue("output", output);
            if (verbose) {
                System.out.println("output = " + output);
            }
            if (null != output) {
                if (output.startsWith("~/")) {
                    output = System.getProperty("user.home") + output.substring(1);
                }
                header = output.substring(0, output.lastIndexOf('.')) + ".h";
            } else {
                output = "out.cpp";
            }
            header = line.getOptionValue("header", header);
            if (verbose) {
                System.out.println("header = " + header);
            }
            if (null != header) {
                if (header.startsWith("~/")) {
                    header = System.getProperty("user.home") + header.substring(1);
                }
            } else {
                header = "out.h";
            }

            if (line.hasOption("packages")) {
                packageprefixes = new HashSet<String>();
                packageprefixes.addAll(Arrays.asList(line.getOptionValues("packages")));
            }
            if (line.hasOption("limit")) {
                limitstring = line.getOptionValue("limit", Integer.toString(DEFAULT_LIMIT));
                limit = Integer.valueOf(limitstring);
            }
            if (line.hasOption("help")) {
                printHelpAndExit(options, args);
            }
        } catch (ParseException exp) {
            if (verbose) {
                System.out.println("Unexpected exception:" + exp.getMessage());
            }
            printHelpAndExit(options, args);
        }

        Set<Class> excludedClasses = new HashSet<>();
        Set<String> foundClassNames = new HashSet<>();
        excludedClasses.add(Object.class);
        excludedClasses.add(String.class);
        excludedClasses.add(void.class);
        excludedClasses.add(Void.class);
        excludedClasses.add(Class.class);
        excludedClasses.add(Enum.class);
        Set<String> packagesSet = new TreeSet<>();
        List<URL> urlsList = new ArrayList<>();
        String cp = System.getProperty("java.class.path");
        if (verbose) {
            System.out.println("System.getProperty(\"java.class.path\") = " + cp);
        }
        if (null != cp) {
            for (String cpe : cp.split(File.pathSeparator)) {
                if (verbose) {
                    System.out.println("class path element = " + cpe);
                }
                File f = new File(cpe);
                if (f.isDirectory()) {
                    urlsList.add(new URL("file:" + f.getCanonicalPath() + File.separator));
                } else if (cpe.endsWith(".jar")) {
                    urlsList.add(new URL("jar:file:" + f.getCanonicalPath() + "!/"));
                }
            }
        }
        cp = System.getenv("CLASSPATH");
        if (verbose) {
            System.out.println("System.getenv(\"CLASSPATH\") = " + cp);
        }
        if (null != cp) {
            for (String cpe : cp.split(File.pathSeparator)) {
                if (verbose) {
                    System.out.println("class path element = " + cpe);
                }
                File f = new File(cpe);
                if (f.isDirectory()) {
                    urlsList.add(new URL("file:" + f.getCanonicalPath() + File.separator));
                } else if (cpe.endsWith(".jar")) {
                    urlsList.add(new URL("jar:file:" + f.getCanonicalPath() + "!/"));
                }
            }
        }
        if (verbose) {
            System.out.println("urlsList = " + urlsList);
        }

        if (null != jar && jar.length() > 0) {
            Path jarPath = FileSystems.getDefault().getPath(jar);
            ZipInputStream zip = new ZipInputStream(Files.newInputStream(jarPath, StandardOpenOption.READ));

            URL jarUrl = new URL("jar:file:" + jarPath.toFile().getCanonicalPath() + "!/");
            urlsList.add(jarUrl);
            URL[] urls = urlsList.toArray(new URL[urlsList.size()]);
            if (verbose) {
                System.out.println("urls = " + Arrays.toString(urls));
            }
            URLClassLoader cl = URLClassLoader.newInstance(urls);
            for (ZipEntry entry = zip.getNextEntry(); entry != null; entry = zip.getNextEntry()) {
                // This ZipEntry represents a class. Now, what class does it represent?
                String entryName = entry.getName();
                if (verbose) {
                    System.out.println("entryName = " + entryName);
                }

                if (!entry.isDirectory() && entryName.endsWith(".class")) {

                    if (entryName.indexOf('$') >= 0) {
                        continue;
                    }
                    String classFileName = entry.getName().replace('/', '.');
                    String className = classFileName.substring(0, classFileName.length() - ".class".length());
                    if (classnamesToFind != null && classnamesToFind.size() > 0
                            && !classnamesToFind.contains(className)) {
                        if (verbose) {
                            System.out.println("skipping className=" + className + " because it does not found in="
                                    + classnamesToFind);
                        }
                        continue;
                    }
                    try {
                        Class clss = cl.loadClass(className);
                        if (null != nativesClassMap && null != nativesNameMap
                                && nativesNameMap.containsKey(className)) {
                            nativesClassMap.put(nativesNameMap.get(className), clss);
                            if (!classes.contains(clss)) {
                                classes.add(clss);
                            }
                        }
                        if (packageprefixes != null && packageprefixes.size() > 0) {
                            if (null == clss.getPackage()) {
                                continue;
                            }
                            final String pkgName = clss.getPackage().getName();
                            boolean matchFound = false;
                            for (String prefix : packageprefixes) {
                                if (pkgName.startsWith(prefix)) {
                                    matchFound = true;
                                    break;
                                }
                            }
                            if (!matchFound) {
                                continue;
                            }
                        }
                        Package p = clss.getPackage();
                        if (null != p) {
                            packagesSet.add(clss.getPackage().getName());
                        }
                        if (!classes.contains(clss) && isAddableClass(clss, excludedClasses)) {
                            if (null != classnamesToFind && classnamesToFind.contains(className)
                                    && !foundClassNames.contains(className)) {
                                foundClassNames.add(className);
                                if (verbose) {
                                    System.out.println("foundClassNames = " + foundClassNames);
                                }
                            }
                            //                        if(verbose) System.out.println("clss = " + clss);
                            classes.add(clss);
                            //                        Class superClass = clss.getSuperclass();
                            //                        while (null != superClass
                            //                                && !classes.contains(superClass)
                            //                                && isAddableClass(superClass, excludedClasses)) {
                            //                            classes.add(superClass);
                            //                            superClass = superClass.getSuperclass();
                            //                        }
                        }
                    } catch (ClassNotFoundException | NoClassDefFoundError ex) {
                        System.err.println(
                                "Caught " + ex.getClass().getName() + ":" + ex.getMessage() + " for className="
                                        + className + ", entryName=" + entryName + ", jarPath=" + jarPath);
                    }
                }
            }
        }
        if (null != classnamesToFind) {
            if (verbose) {
                System.out.println("Checking classnames arguments");
            }
            for (String classname : classnamesToFind) {
                if (verbose) {
                    System.out.println("classname = " + classname);
                }
                if (foundClassNames.contains(classname)) {
                    if (verbose) {
                        System.out.println("foundClassNames.contains(" + classname + ")");
                    }
                    continue;
                }
                try {
                    if (classes.contains(Class.forName(classname))) {
                        if (verbose) {
                            System.out.println("Classes list already contains:  " + classname);
                        }
                        continue;
                    }
                } catch (Exception e) {

                }

                if (null != classname && classname.length() > 0) {
                    urlsList.add(new URL("file://" + System.getProperty("user.dir") + "/"));

                    URL[] urls = urlsList.toArray(new URL[urlsList.size()]);
                    if (verbose) {
                        System.out.println("urls = " + Arrays.toString(urls));
                    }
                    URLClassLoader cl = URLClassLoader.newInstance(urls);
                    Class c = null;
                    try {
                        c = cl.loadClass(classname);
                    } catch (ClassNotFoundException e) {
                        System.err.println("Class " + classname + " not found ");
                    }
                    if (verbose) {
                        System.out.println("c = " + c);
                    }
                    if (null == c) {
                        try {
                            c = ClassLoader.getSystemClassLoader().loadClass(classname);
                        } catch (ClassNotFoundException e) {
                            if (verbose) {
                                System.out.println("System ClassLoader failed to find " + classname);
                            }
                        }
                    }
                    if (null != c) {
                        classes.add(c);
                    } else {
                        System.err.println("Class " + classname + " not found");
                    }
                }
            }
            if (verbose) {
                System.out.println("Finished checking classnames arguments");
            }
        }
        if (classes == null || classes.size() < 1) {
            if (null == nativesClassMap || nativesClassMap.keySet().size() < 1) {
                System.err.println("No Classes specified or found.");
                System.exit(1);
            }
        }
        if (verbose) {
            System.out.println("Classes found = " + classes.size());
        }
        if (classes.size() > limit) {
            System.err.println("limit=" + limit);
            System.err.println(
                    "Too many classes please use -c or -p options to limit classes or -l to increase limit.");
            if (verbose) {
                System.out.println("packagesSet = " + packagesSet);
            }
            System.exit(1);
        }
        List<Class> newClasses = new ArrayList<Class>();
        for (Class clss : classes) {
            Class superClass = clss.getSuperclass();
            while (null != superClass && !classes.contains(superClass) && !newClasses.contains(superClass)
                    && isAddableClass(superClass, excludedClasses)) {
                newClasses.add(superClass);
                superClass = superClass.getSuperclass();
            }
            try {
                Field fa[] = clss.getDeclaredFields();
                for (Field f : fa) {
                    if (Modifier.isPublic(f.getModifiers())) {
                        Class fClass = f.getType();
                        if (!classes.contains(fClass) && !newClasses.contains(fClass)
                                && isAddableClass(fClass, excludedClasses)) {
                            newClasses.add(fClass);
                        }
                    }
                }
            } catch (NoClassDefFoundError e) {
                e.printStackTrace();
            }
            for (Method m : clss.getDeclaredMethods()) {
                if (m.isSynthetic()) {
                    continue;
                }
                if (!Modifier.isPublic(m.getModifiers()) || Modifier.isAbstract(m.getModifiers())) {
                    continue;
                }
                Class retType = m.getReturnType();
                if (verbose) {
                    System.out.println("Checking dependancies for Method = " + m);
                }
                if (!classes.contains(retType) && !newClasses.contains(retType)
                        && isAddableClass(retType, excludedClasses)) {
                    newClasses.add(retType);
                    superClass = retType.getSuperclass();
                    while (null != superClass && !classes.contains(superClass) && !newClasses.contains(superClass)
                            && isAddableClass(superClass, excludedClasses)) {
                        newClasses.add(superClass);
                        superClass = superClass.getSuperclass();
                    }
                }
                for (Class paramType : m.getParameterTypes()) {
                    if (!classes.contains(paramType) && !newClasses.contains(paramType)
                            && isAddableClass(paramType, excludedClasses)) {
                        newClasses.add(paramType);
                        superClass = paramType.getSuperclass();
                        while (null != superClass && !classes.contains(superClass)
                                && !newClasses.contains(superClass) && !excludedClasses.contains(superClass)) {
                            newClasses.add(superClass);
                            superClass = superClass.getSuperclass();
                        }
                    }
                }
            }
        }
        if (null != nativesClassMap) {
            for (Class clss : nativesClassMap.values()) {
                if (null != clss) {
                    Class superClass = clss.getSuperclass();
                    while (null != superClass && !classes.contains(superClass) && !newClasses.contains(superClass)
                            && !excludedClasses.contains(superClass)) {
                        newClasses.add(superClass);
                        superClass = superClass.getSuperclass();
                    }
                }
            }
        }
        if (verbose) {
            System.out.println("Dependency classes needed = " + newClasses.size());
        }
        classes.addAll(newClasses);
        List<Class> newOrderClasses = new ArrayList<>();
        for (Class clss : classes) {
            if (newOrderClasses.contains(clss)) {
                continue;
            }
            Class superClass = clss.getSuperclass();
            Stack<Class> stack = new Stack<>();
            while (null != superClass && !newOrderClasses.contains(superClass)
                    && !superClass.equals(java.lang.Object.class)) {
                stack.push(superClass);
                superClass = superClass.getSuperclass();
            }
            while (!stack.empty()) {
                newOrderClasses.add(stack.pop());
            }
            newOrderClasses.add(clss);
        }
        classes = newOrderClasses;
        if (verbose) {
            System.out.println("Total number of classes = " + classes.size());
            System.out.println("classes = " + classes);
        }

        String forward_header = header.substring(0, header.lastIndexOf('.')) + "_fwd.h";
        Map<String, String> map = new HashMap<>();
        map.put(JAR, jar != null ? jar : "");
        map.put("%CLASSPATH_PREFIX%",
                getCurrentDir() + ((jar != null)
                        ? (File.pathSeparator + ((new File(jar).getCanonicalPath()).replace("\\", "\\\\")))
                        : ""));
        map.put("%FORWARD_HEADER%", forward_header);
        map.put("%HEADER%", header);
        map.put("%PATH_SEPERATOR%", File.pathSeparator);
        String tabs = "";
        String headerDefine = forward_header.substring(Math.max(0, forward_header.indexOf(File.separator)))
                .replace(".", "_");
        map.put(HEADER_DEFINE, headerDefine);
        map.put(NAMESPACE, namespace);
        if (null != nativesClassMap) {
            for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                final Class javaClass = e.getValue();
                final String nativeClassName = e.getKey();
                try (PrintWriter pw = new PrintWriter(new FileWriter(nativeClassName + ".java"))) {
                    if (javaClass.isInterface()) {
                        pw.println("public class " + nativeClassName + " implements " + javaClass.getCanonicalName()
                                + ", AutoCloseable{");
                    } else {
                        pw.println("public class " + nativeClassName + " extends " + javaClass.getCanonicalName()
                                + " implements AutoCloseable{");
                    }
                    if (null != loadlibname && loadlibname.length() > 0) {
                        pw.println(TAB_STRING + "static {");
                        pw.println(TAB_STRING + TAB_STRING + "System.loadLibrary(\"" + loadlibname + "\");");
                        pw.println(TAB_STRING + "}");
                        pw.println();
                    }
                    pw.println(TAB_STRING + "public " + nativeClassName + "() {");
                    pw.println(TAB_STRING + "}");
                    pw.println();
                    pw.println(TAB_STRING + "private long nativeAddress=0;");
                    pw.println(TAB_STRING + "private boolean nativeDeleteOnClose=false;");
                    pw.println();

                    pw.println(TAB_STRING + "protected " + nativeClassName + "(final long nativeAddress) {");
                    pw.println(TAB_STRING + TAB_STRING + "this.nativeAddress = nativeAddress;");
                    pw.println(TAB_STRING + "}");

                    pw.println(TAB_STRING + "private native void nativeDelete();");
                    pw.println();
                    pw.println(TAB_STRING + "@Override");
                    pw.println(TAB_STRING + "public synchronized void  close() {");
                    //                        pw.println(TAB_STRING + TAB_STRING + "if(nativeAddress != 0 && nativeDeleteOnClose) {");
                    pw.println(TAB_STRING + TAB_STRING + "nativeDelete();");
                    //                        pw.println(TAB_STRING + TAB_STRING + "}");
                    //                        pw.println(TAB_STRING + TAB_STRING + "nativeAddress=0;");
                    //                        pw.println(TAB_STRING + TAB_STRING + "nativeDeleteOnClose=false;");
                    pw.println(TAB_STRING + "}");

                    pw.println();
                    pw.println(TAB_STRING + "protected void finalizer() {");
                    pw.println(TAB_STRING + TAB_STRING + "close();");
                    pw.println(TAB_STRING + "}");

                    pw.println();
                    Method ma[] = javaClass.getDeclaredMethods();
                    for (Method m : ma) {
                        int modifiers = m.getModifiers();
                        if (Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers)
                                && !Modifier.isStatic(modifiers)
                                //                                && !m.isDefault()
                                && !m.isSynthetic()) {
                            pw.println();
                            pw.println(TAB_STRING + "@Override");
                            String params = "";
                            for (int i = 0; i < m.getParameterTypes().length; i++) {
                                params += m.getParameterTypes()[i].getCanonicalName() + " param" + i;
                                if (i < m.getParameterTypes().length - 1) {
                                    params += ",";
                                }
                            }
                            pw.println(TAB_STRING + "native public " + m.getReturnType().getCanonicalName() + " "
                                    + m.getName() + "(" + params + ");");
                            //                                    + IntStream.range(0, m.getParameterTypes().length)
                            //                                    .mapToObj(i -> m.getParameterTypes()[i].getCanonicalName() + " param" + i)
                            //                                    .collect(Collectors.joining(",")) + ");");
                        }
                    }
                    pw.println("}");
                }
            }
        }
        try (PrintWriter pw = new PrintWriter(new FileWriter(forward_header))) {
            tabs = "";
            processTemplate(pw, map, "header_fwd_template_start.h", tabs);
            Class lastClass = null;
            for (int class_index = 0; class_index < classes.size(); class_index++) {
                Class clss = classes.get(class_index);
                String clssOnlyName = getCppClassName(clss);
                tabs = openClassNamespace(clss, pw, tabs, lastClass);
                tabs += TAB_STRING;
                pw.println(tabs + "class " + clssOnlyName + ";");
                tabs = tabs.substring(0, tabs.length() - 1);
                Class nextClass = (class_index < (classes.size() - 1)) ? classes.get(class_index + 1) : null;
                tabs = closeClassNamespace(clss, pw, tabs, nextClass);
                lastClass = clss;
            }
            processTemplate(pw, map, "header_fwd_template_end.h", tabs);
        }
        headerDefine = header.substring(Math.max(0, header.indexOf(File.separator))).replace(".", "_");
        map.put(HEADER_DEFINE, headerDefine);
        map.put(NAMESPACE, namespace);
        if (classes_per_file < 1) {
            classes_per_file = classes.size();
        }
        final int num_class_segments = (classes.size() > 1)
                ? ((classes.size() + classes_per_file - 1) / classes_per_file)
                : 1;
        String fmt = "%d";
        if (num_class_segments > 10) {
            fmt = "%02d";
        }
        if (num_class_segments > 100) {
            fmt = "%03d";
        }
        String header_file_base = header;
        if (header_file_base.endsWith(".h")) {
            header_file_base = header_file_base.substring(0, header_file_base.length() - 2);
        } else if (header_file_base.endsWith(".hh")) {
            header_file_base = header_file_base.substring(0, header_file_base.length() - 3);
        } else if (header_file_base.endsWith(".hpp")) {
            header_file_base = header_file_base.substring(0, header_file_base.length() - 4);
        }
        try (PrintWriter pw = new PrintWriter(new FileWriter(header))) {
            tabs = "";
            processTemplate(pw, map, HEADER_TEMPLATE_STARTH, tabs);
            for (int segment_index = 0; segment_index < num_class_segments; segment_index++) {
                String header_segment_file = header_file_base + String.format(fmt, segment_index) + ".h";
                pw.println("#include \"" + header_segment_file + "\"");
            }
            if (null != nativesClassMap) {
                tabs = TAB_STRING;
                for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                    final Class javaClass = e.getValue();
                    final String nativeClassName = e.getKey();
                    pw.println();
                    pw.println(tabs + "class " + nativeClassName + "Context;");
                    pw.println();
                    map.put(CLASS_NAME, nativeClassName);
                    map.put("%BASE_CLASS_FULL_NAME%",
                            "::" + namespace + "::" + getModifiedClassName(javaClass).replace(".", "::"));
                    map.put(OBJECT_CLASS_FULL_NAME, "::" + namespace + "::java::lang::Object");
                    processTemplate(pw, map, HEADER_CLASS_STARTH, tabs);
                    tabs += TAB_STRING;
                    pw.println(tabs + nativeClassName + "Context *context;");
                    pw.println(tabs + nativeClassName + "();");
                    pw.println(tabs + "~" + nativeClassName + "();");
                    Method methods[] = javaClass.getDeclaredMethods();
                    for (int j = 0; j < methods.length; j++) {
                        Method method = methods[j];
                        int modifiers = method.getModifiers();
                        if (!Modifier.isPublic(modifiers)) {
                            continue;
                        }
                        if (Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers)
                                && !Modifier.isStatic(modifiers)
                                //                                && !method.isDefault()
                                && !method.isSynthetic()) {
                            pw.println(tabs + getNativeMethodCppDeclaration(method, javaClass));
                        }
                    }
                    pw.println(tabs + "void initContext(" + nativeClassName + "Context *ctx,bool isref);");
                    pw.println(tabs + "void deleteContext();");
                    tabs = tabs.substring(TAB_STRING.length());
                    pw.println(tabs + "}; // end class " + nativeClassName);
                }
            }
            tabs = "";
            processTemplate(pw, map, HEADER_TEMPLATE_ENDH, tabs);
        }
        for (int segment_index = 0; segment_index < num_class_segments; segment_index++) {
            String header_segment_file = header_file_base + String.format(fmt, segment_index) + ".h";
            try (PrintWriter pw = new PrintWriter(new FileWriter(header_segment_file))) {
                tabs = "";
                //processTemplate(pw, map, HEADER_TEMPLATE_STARTH, tabs);
                pw.println("// Never include this file (" + header_segment_file + ") directly. include " + header
                        + " instead.");
                pw.println();
                Class lastClass = null;
                final int start_segment_index = segment_index * classes_per_file;
                final int end_segment_index = Math.min(segment_index * classes_per_file + classes_per_file,
                        classes.size());
                List<Class> classesSegList = classes.subList(start_segment_index, end_segment_index);
                pw.println();
                pw.println(tabs + "// start_segment_index = " + start_segment_index);
                pw.println(tabs + "// start_segment_index = " + end_segment_index);
                pw.println(tabs + "// segment_index = " + segment_index);
                pw.println(tabs + "// classesSegList=" + classesSegList);
                pw.println();
                for (int class_index = 0; class_index < classesSegList.size(); class_index++) {
                    Class clss = classesSegList.get(class_index);
                    pw.println();
                    pw.println(tabs + "// class_index = " + class_index + " clss=" + clss);
                    pw.println();
                    String clssName = clss.getCanonicalName();
                    tabs = openClassNamespace(clss, pw, tabs, lastClass);
                    String clssOnlyName = getCppClassName(clss);
                    map.put(CLASS_NAME, clssOnlyName);
                    map.put("%BASE_CLASS_FULL_NAME%", classToCppBase(clss));
                    map.put(OBJECT_CLASS_FULL_NAME, getCppRelativeName(Object.class, clss));
                    tabs += TAB_STRING;
                    processTemplate(pw, map, HEADER_CLASS_STARTH, tabs);
                    tabs += TAB_STRING;

                    Constructor constructors[] = clss.getDeclaredConstructors();
                    if (!hasNoArgConstructor(constructors)) {
                        if (Modifier.isAbstract(clss.getModifiers()) || clss.isInterface()) {
                            pw.println(tabs + "protected:");
                            pw.println(tabs + clssOnlyName + "() {};");
                            pw.println(tabs + "public:");
                        } else {
                            if (constructors.length > 0) {
                                pw.println(tabs + "protected:");
                            }
                            pw.println(tabs + clssOnlyName + "();");
                            if (constructors.length > 0) {
                                pw.println(tabs + "public:");
                            }
                        }
                    }
                    for (Constructor c : constructors) {
                        if (c.getParameterTypes().length == 0 && Modifier.isProtected(c.getModifiers())) {
                            pw.println(tabs + "protected:");
                            pw.println(tabs + clssOnlyName + "();");
                            pw.println(tabs + "public:");
                        }
                        if (checkConstructor(c, clss, classes)) {
                            continue;
                        }

                        if (c.getParameterTypes().length == 1 && clss.isAssignableFrom(c.getParameterTypes()[0])) {
                            continue;
                        }
                        if (!Modifier.isPublic(c.getModifiers())) {
                            continue;
                        }
                        if (c.getParameterTypes().length == 1) {
                            if (c.getParameterTypes()[0].getName().equals(clss.getName())) {
                                //                                    if(verbose) System.out.println("skipping constructor.");
                                continue;
                            }
                        }

                        if (!checkParameters(c.getParameterTypes(), classes)) {
                            continue;
                        }
                        pw.println(
                                tabs + clssOnlyName + getCppParamDeclarations(c.getParameterTypes(), clss) + ";");
                        if (isConstructorToMakeEasy(c, clss)) {
                            pw.println(tabs + clssOnlyName
                                    + getEasyCallCppParamDeclarations(c.getParameterTypes(), clss) + ";");
                        }
                    }

                    pw.println(tabs + "~" + clssOnlyName + "();");
                    Field fa[] = clss.getDeclaredFields();
                    for (int findex = 0; findex < fa.length; findex++) {
                        Field field = fa[findex];
                        if (addGetterMethod(field, clss, classes)) {
                            pw.println(tabs + getCppFieldGetterDeclaration(field, clss));
                        }
                        if (addSetterMethod(field, clss, classes)) {
                            pw.println(tabs + getCppFieldSetterDeclaration(field, clss));
                        }
                    }
                    Method methods[] = clss.getDeclaredMethods();
                    for (int j = 0; j < methods.length; j++) {
                        Method method = methods[j];
                        if (!checkMethod(method, classes)) {
                            continue;
                        }
                        if ((method.getModifiers() & Modifier.PUBLIC) == Modifier.PUBLIC) {
                            pw.println(tabs + getCppDeclaration(method, clss));
                        }
                        if (isArrayStringMethod(method)) {
                            pw.println(tabs + getCppModifiers(method.getModifiers())
                                    + getCppType(method.getReturnType(), clss) + " " + fixMethodName(method)
                                    + "(int argc,const char **argv);");
                        }
                        if (isMethodToMakeEasy(method)) {
                            pw.println(tabs + getEasyCallCppDeclaration(method, clss));
                        }
                    }
                    tabs = tabs.substring(TAB_STRING.length());
                    pw.println(tabs + "}; // end class " + clssOnlyName);
                    tabs = tabs.substring(0, tabs.length() - 1);
                    Class nextClass = (class_index < (classesSegList.size() - 1))
                            ? classesSegList.get(class_index + 1)
                            : null;
                    tabs = closeClassNamespace(clss, pw, tabs, nextClass);
                    pw.println();
                    lastClass = clss;
                }
                //processTemplate(pw, map, HEADER_TEMPLATE_ENDH, tabs);
            }
        }
        for (int segment_index = 0; segment_index < num_class_segments; segment_index++) {
            String output_segment_file = output;
            if (output_segment_file.endsWith(".cpp")) {
                output_segment_file = output_segment_file.substring(0, output_segment_file.length() - 4);
            } else if (output_segment_file.endsWith(".cc")) {
                output_segment_file = output_segment_file.substring(0, output_segment_file.length() - 3);
            }
            output_segment_file += "" + String.format(fmt, segment_index) + ".cpp";
            try (PrintWriter pw = new PrintWriter(new FileWriter(output_segment_file))) {
                tabs = "";
                if (segment_index < 1) {
                    processTemplate(pw, map, "cpp_template_start_first.cpp", tabs);
                } else {
                    processTemplate(pw, map, CPP_TEMPLATE_STARTCPP, tabs);
                }
                final int start_segment_index = segment_index * classes_per_file;
                final int end_segment_index = Math.min(segment_index * classes_per_file + classes_per_file,
                        classes.size());
                List<Class> classesSegList = classes.subList(start_segment_index, end_segment_index);
                pw.println();
                pw.println(tabs + "// start_segment_index = " + start_segment_index);
                pw.println(tabs + "// start_segment_index = " + end_segment_index);
                pw.println(tabs + "// segment_index = " + segment_index);
                pw.println(tabs + "// classesSegList=" + classesSegList);
                pw.println();
                Class lastClass = null;
                for (int class_index = 0; class_index < classesSegList.size(); class_index++) {
                    Class clss = classesSegList.get(class_index);
                    pw.println();
                    pw.println(tabs + "// class_index = " + class_index + " clss=" + clss);
                    pw.println();
                    String clssName = clss.getCanonicalName();
                    tabs = openClassNamespace(clss, pw, tabs, lastClass);
                    String clssOnlyName = getCppClassName(clss);
                    map.put(CLASS_NAME, clssOnlyName);
                    map.put("%BASE_CLASS_FULL_NAME%", classToCppBase(clss));

                    map.put(FULL_CLASS_NAME, clssName);
                    String fcjs = classToJNISignature(clss);
                    fcjs = fcjs.substring(1, fcjs.length() - 1);
                    map.put(FULL_CLASS_JNI_SIGNATURE, fcjs);
                    if (verbose) {
                        System.out.println("fcjs = " + fcjs);
                    }
                    map.put(OBJECT_CLASS_FULL_NAME, getCppRelativeName(Object.class, clss));
                    map.put("%INITIALIZE_CONTEXT%", "");
                    map.put("%INITIALIZE_CONTEXT_REF%", "");
                    processTemplate(pw, map, CPP_START_CLASSCPP, tabs);
                    Constructor constructors[] = clss.getDeclaredConstructors();

                    if (!hasNoArgConstructor(constructors)) {
                        if (!Modifier.isAbstract(clss.getModifiers()) && !clss.isInterface()) {
                            pw.println(tabs + clssOnlyName + "::" + clssOnlyName + "() : " + classToCppBase(clss)
                                    + "((jobject)NULL,false) {");
                            map.put(JNI_SIGNATURE, "()V");
                            map.put(CONSTRUCTOR_ARGS, "");
                            processTemplate(pw, map, CPP_NEWCPP, tabs);
                            pw.println(tabs + "}");
                            pw.println();
                        }

                    }
                    for (Constructor c : constructors) {
                        if (checkConstructor(c, clss, classes)) {
                            continue;
                        }
                        Class[] paramClasses = c.getParameterTypes();
                        pw.println(tabs + clssOnlyName + "::" + clssOnlyName
                                + getCppParamDeclarations(paramClasses, clss) + " : " + classToCppBase(clss)
                                + "((jobject)NULL,false) {");
                        tabs = tabs + TAB_STRING;
                        map.put(JNI_SIGNATURE, "(" + getJNIParamSignature(paramClasses) + ")V");
                        map.put(CONSTRUCTOR_ARGS,
                                (paramClasses.length > 0 ? "," : "") + getCppParamNames(paramClasses));
                        processTemplate(pw, map, CPP_NEWCPP, tabs);
                        tabs = tabs.substring(0, tabs.length() - 1);
                        pw.println(tabs + "}");
                        pw.println();
                        if (isConstructorToMakeEasy(c, clss)) {
                            pw.println(tabs + clssOnlyName + "::" + clssOnlyName
                                    + getEasyCallCppParamDeclarations(c.getParameterTypes(), clss) + " : "
                                    + classToCppBase(clss) + "((jobject)NULL,false) {");
                            processTemplate(pw, map, "cpp_start_easy_constructor.cpp", tabs);
                            for (int paramIndex = 0; paramIndex < paramClasses.length; paramIndex++) {
                                Class paramClasse = paramClasses[paramIndex];
                                String parmName = getParamNameIn(paramClasse, paramIndex);
                                if (isString(paramClasse)) {
                                    pw.println(tabs + "jstring " + parmName + " = env->NewStringUTF(easyArg_"
                                            + paramIndex + ");");
                                } else if (isPrimitiveArray(paramClasse)) {
                                    String callString = getMethodCallString(paramClasse.getComponentType());
                                    pw.println(tabs + getCppArrayType(paramClasse.getComponentType()) + " "
                                            + classToParamNameDecl(paramClasse, paramIndex) + "= env->New"
                                            + callString + "Array(easyArg_" + paramIndex + "_length);");
                                    pw.println(tabs + "env->Set" + callString + "ArrayRegion("
                                            + classToParamNameDecl(paramClasse, paramIndex) + ",0,easyArg_"
                                            + paramIndex + "_length,easyArg_" + paramIndex + ");");
                                } else {
                                    pw.println(tabs + getCppType(paramClasse, clss) + " "
                                            + classToParamNameDecl(paramClasse, paramIndex) + "= easyArg_"
                                            + paramIndex + ";");
                                }
                            }
                            processTemplate(pw, map, "cpp_new_easy_internals.cpp", tabs);
                            for (int paramIndex = 0; paramIndex < paramClasses.length; paramIndex++) {
                                Class paramClasse = paramClasses[paramIndex];
                                String parmName = getParamNameIn(paramClasse, paramIndex);
                                if (isString(paramClasse)) {
                                    pw.println(tabs + "jobjectRefType ref_" + paramIndex
                                            + " = env->GetObjectRefType(" + parmName + ");");
                                    pw.println(tabs + "if(ref_" + paramIndex + " == JNIGlobalRefType) {");
                                    pw.println(tabs + TAB_STRING + "env->DeleteGlobalRef(" + parmName + ");");
                                    pw.println(tabs + "}");
                                } else if (isPrimitiveArray(paramClasse)) {
                                    String callString = getMethodCallString(paramClasse.getComponentType());
                                    pw.println(tabs + "env->Get" + callString + "ArrayRegion("
                                            + classToParamNameDecl(paramClasse, paramIndex) + ",0,easyArg_"
                                            + paramIndex + "_length,easyArg_" + paramIndex + ");");
                                    pw.println(tabs + "jobjectRefType ref_" + paramIndex
                                            + " = env->GetObjectRefType(" + parmName + ");");
                                    pw.println(tabs + "if(ref_" + paramIndex + " == JNIGlobalRefType) {");
                                    pw.println(tabs + TAB_STRING + "env->DeleteGlobalRef(" + parmName + ");");
                                    pw.println(tabs + "}");
                                } else {

                                }
                            }
                            processTemplate(pw, map, "cpp_end_easy_constructor.cpp", tabs);
                            pw.println(tabs + "}");
                        }
                    }

                    pw.println();
                    pw.println(tabs + "// Destructor for " + clssName);
                    pw.println(tabs + clssOnlyName + "::~" + clssOnlyName + "() {");
                    pw.println(tabs + "\t// Place-holder for later extensibility.");
                    pw.println(tabs + "}");
                    pw.println();
                    Field fa[] = clss.getDeclaredFields();
                    for (int findex = 0; findex < fa.length; findex++) {
                        Field field = fa[findex];
                        if (addGetterMethod(field, clss, classes)) {
                            pw.println();
                            pw.println(tabs + "// Field getter for " + field.getName());
                            pw.println(getCppFieldGetterDefinitionStart(tabs, clssOnlyName, field, clss));
                            map.put("%FIELD_NAME%", field.getName());
                            map.put(JNI_SIGNATURE, classToJNISignature(field.getType()));
                            Class returnClass = field.getType();
                            map.put(METHOD_ONFAIL, getOnFailString(returnClass, clss));
                            map.put(METHOD_ARGS, "");
                            map.put("%METHOD_RETURN%", isVoid(returnClass) ? "" : "return");
                            map.put("%METHOD_CALL_TYPE%", getMethodCallString(returnClass));
                            map.put("%METHOD_RETURN_TYPE%", getCppType(returnClass, clss));
                            map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclare(returnClass));
                            String retStore = isVoid(returnClass) ? ""
                                    : "retVal= (" + getMethodReturnVarType(returnClass) + ") ";
                            map.put("%METHOD_RETURN_STORE%", retStore);
                            map.put("%METHOD_RETURN_GET%", getMethodReturnGet(tabs, returnClass, clss));

                            if (Modifier.isStatic(field.getModifiers())) {
                                processTemplate(pw, map, "cpp_static_getfield.cpp", tabs);
                            } else {
                                processTemplate(pw, map, "cpp_getfield.cpp", tabs);
                            }

                            pw.println(tabs + "}");
                        }
                        if (addSetterMethod(field, clss, classes)) {
                            pw.println();
                            pw.println(tabs + "// Field setter for " + field.getName());
                            pw.println(getCppFieldSetterDefinitionStart(tabs, clssOnlyName, field, clss));
                            map.put("%FIELD_NAME%", field.getName());
                            map.put(JNI_SIGNATURE, classToJNISignature(field.getType()));
                            Class returnClass = void.class;
                            map.put(METHOD_ONFAIL, getOnFailString(returnClass, clss));
                            Class[] paramClasses = new Class[] { field.getType() };
                            String methodArgs = getCppParamNames(paramClasses);
                            map.put(METHOD_ARGS, (paramClasses.length > 0 ? "," : "") + methodArgs);
                            map.put("%METHOD_RETURN%", isVoid(returnClass) ? "" : "return");
                            map.put("%METHOD_CALL_TYPE%", getMethodCallString(field.getType()));
                            map.put("%METHOD_RETURN_TYPE%", getCppType(returnClass, clss));
                            map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclare(returnClass));
                            String retStore = isVoid(returnClass) ? ""
                                    : "retVal= (" + getMethodReturnVarType(returnClass) + ") ";
                            map.put("%METHOD_RETURN_STORE%", retStore);
                            map.put("%METHOD_RETURN_GET%", getMethodReturnGet(tabs, returnClass, clss));

                            if (Modifier.isStatic(field.getModifiers())) {
                                processTemplate(pw, map, "cpp_static_setfield.cpp", tabs);
                            } else {
                                processTemplate(pw, map, "cpp_setfield.cpp", tabs);
                            }

                            pw.println(tabs + "}");
                        }
                    }
                    Method methods[] = clss.getDeclaredMethods();
                    for (int j = 0; j < methods.length; j++) {
                        Method method = methods[j];

                        if (checkMethod(method, classes)) {
                            pw.println();
                            pw.println(getCppMethodDefinitionStart(tabs, clssOnlyName, method, clss));
                            map.put(METHOD_NAME, method.getName());
                            if (fixMethodName(method).contains("equals2")) {
                                if (verbose) {
                                    System.out.println("debug me");
                                }
                            }
                            map.put("%JAVA_METHOD_NAME%", method.getName());
                            Class[] paramClasses = method.getParameterTypes();
                            String methodArgs = getCppParamNames(paramClasses);
                            map.put(METHOD_ARGS, (paramClasses.length > 0 ? "," : "") + methodArgs);
                            Class returnClass = method.getReturnType();
                            map.put(JNI_SIGNATURE, "(" + getJNIParamSignature(paramClasses) + ")"
                                    + classToJNISignature(returnClass));
                            map.put(METHOD_ONFAIL, getOnFailString(returnClass, clss));
                            map.put("%METHOD_RETURN%", isVoid(returnClass) ? "" : "return");
                            map.put("%METHOD_CALL_TYPE%", getMethodCallString(returnClass));
                            map.put("%METHOD_RETURN_TYPE%", getCppType(returnClass, clss));
                            map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclare(returnClass));
                            String retStore = isVoid(returnClass) ? ""
                                    : "retVal= (" + getMethodReturnVarType(returnClass) + ") ";
                            map.put("%METHOD_RETURN_STORE%", retStore);
                            map.put("%METHOD_RETURN_GET%", getMethodReturnGet(tabs, returnClass, clss));
                            tabs += TAB_STRING;
                            if (!Modifier.isStatic(method.getModifiers())) {
                                processTemplate(pw, map, CPP_METHODCPP, tabs);
                            } else {
                                processTemplate(pw, map, CPP_STATIC_METHODCPP, tabs);
                            }
                            tabs = tabs.substring(0, tabs.length() - TAB_STRING.length());
                            pw.println(tabs + "}");
                            if (isArrayStringMethod(method)) {
                                map.put(METHOD_RETURN_STORE,
                                        isVoid(returnClass) ? "" : getCppType(returnClass, clss) + " returnVal=");
                                map.put(METHOD_RETURN_GET, isVoid(returnClass) ? "return ;" : "return returnVal;");
                                processTemplate(pw, map, CPP_EASY_STRING_ARRAY_METHODCPP, tabs);
                            } else if (isMethodToMakeEasy(method)) {
                                pw.println();
                                pw.println(tabs + "// Easy call alternative for " + method.getName());
                                pw.println(getEasyCallCppMethodDefinitionStart(tabs, clssOnlyName, method, clss));
                                tabs += TAB_STRING;
                                map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclareOut(returnClass, clss));
                                processTemplate(pw, map, "cpp_start_easy_method.cpp", tabs);
                                for (int paramIndex = 0; paramIndex < paramClasses.length; paramIndex++) {
                                    Class paramClasse = paramClasses[paramIndex];
                                    String parmName = getParamNameIn(paramClasse, paramIndex);
                                    if (isString(paramClasse)) {
                                        pw.println(tabs + "jstring " + parmName + " = env->NewStringUTF(easyArg_"
                                                + paramIndex + ");");
                                    } else if (isPrimitiveArray(paramClasse)) {
                                        String callString = getMethodCallString(paramClasse.getComponentType());
                                        pw.println(tabs + getCppArrayType(paramClasse.getComponentType()) + " "
                                                + classToParamNameDecl(paramClasse, paramIndex) + "= env->New"
                                                + callString + "Array(easyArg_" + paramIndex + "_length);");
                                        pw.println(tabs + "env->Set" + callString + "ArrayRegion("
                                                + classToParamNameDecl(paramClasse, paramIndex) + ",0,easyArg_"
                                                + paramIndex + "_length,easyArg_" + paramIndex + ");");
                                    } else {
                                        pw.println(tabs + getCppType(paramClasse, clss) + " "
                                                + classToParamNameDecl(paramClasse, paramIndex) + "= easyArg_"
                                                + paramIndex + ";");
                                    }
                                }
                                String methodArgsIn = getCppParamNamesIn(paramClasses);
                                String retStoreOut = isVoid(returnClass) ? ""
                                        : "retVal= (" + getMethodReturnOutVarType(returnClass, clss) + ") ";

                                pw.println(tabs + retStoreOut + fixMethodName(method) + "(" + methodArgsIn + ");");
                                for (int paramIndex = 0; paramIndex < paramClasses.length; paramIndex++) {
                                    Class paramClasse = paramClasses[paramIndex];
                                    String parmName = getParamNameIn(paramClasse, paramIndex);
                                    if (isString(paramClasse)) {
                                        pw.println(tabs + "jobjectRefType ref_" + paramIndex
                                                + " = env->GetObjectRefType(" + parmName + ");");
                                        pw.println(tabs + "if(ref_" + paramIndex + " == JNIGlobalRefType) {");
                                        pw.println(tabs + TAB_STRING + "env->DeleteGlobalRef(" + parmName + ");");
                                        pw.println(tabs + "}");
                                    } else if (isPrimitiveArray(paramClasse)) {
                                        String callString = getMethodCallString(paramClasse.getComponentType());
                                        pw.println(tabs + "env->Get" + callString + "ArrayRegion("
                                                + classToParamNameDecl(paramClasse, paramIndex) + ",0,easyArg_"
                                                + paramIndex + "_length,easyArg_" + paramIndex + ");");
                                        pw.println(tabs + "jobjectRefType ref_" + paramIndex
                                                + " = env->GetObjectRefType(" + parmName + ");");
                                        pw.println(tabs + "if(ref_" + paramIndex + " == JNIGlobalRefType) {");
                                        pw.println(tabs + TAB_STRING + "env->DeleteGlobalRef(" + parmName + ");");
                                        pw.println(tabs + "}");
                                    } else {

                                    }
                                }
                                processTemplate(pw, map, "cpp_end_easy_method.cpp", tabs);
                                if (!isVoid(returnClass)) {
                                    pw.println(tabs + "return retVal;");
                                }
                                tabs = tabs.substring(TAB_STRING.length());
                                pw.println(tabs + "}");
                                pw.println();
                            }
                        }
                    }
                    processTemplate(pw, map, CPP_END_CLASSCPP, tabs);
                    tabs = tabs.substring(0, tabs.length() - 1);
                    Class nextClass = (class_index < (classesSegList.size() - 1))
                            ? classesSegList.get(class_index + 1)
                            : null;
                    tabs = closeClassNamespace(clss, pw, tabs, nextClass);
                    lastClass = clss;
                }

                if (segment_index < 1) {
                    if (null != nativesClassMap) {
                        for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                            final Class javaClass = e.getValue();
                            final String nativeClassName = e.getKey();
                            map.put(CLASS_NAME, nativeClassName);
                            map.put(FULL_CLASS_NAME, nativeClassName);
                            map.put(FULL_CLASS_JNI_SIGNATURE, nativeClassName);
                            map.put("%BASE_CLASS_FULL_NAME%",
                                    "::" + namespace + "::" + getModifiedClassName(javaClass).replace(".", "::"));
                            map.put(OBJECT_CLASS_FULL_NAME, "::" + namespace + "::java::lang::Object");
                            map.put("%INITIALIZE_CONTEXT%", "context=NULL; initContext(NULL,false);");
                            map.put("%INITIALIZE_CONTEXT_REF%", "context=NULL; initContext(objref.context,true);");
                            tabs += TAB_STRING;

                            processTemplate(pw, map, CPP_START_CLASSCPP, tabs);
                            pw.println();
                            pw.println(tabs + nativeClassName + "::" + nativeClassName + "() : "
                                    + getModifiedClassName(javaClass).replace(".", "::")
                                    + "((jobject)NULL,false) {");
                            tabs += TAB_STRING;
                            pw.println(tabs + "context=NULL;");
                            pw.println(tabs + "initContext(NULL,false);");
                            map.put(JNI_SIGNATURE, "()V");
                            map.put(CONSTRUCTOR_ARGS, "");
                            processTemplate(pw, map, "cpp_new_native.cpp", tabs);
                            tabs = tabs.substring(TAB_STRING.length());
                            pw.println(tabs + "}");
                            pw.println();
                            pw.println(tabs + "// Destructor for " + nativeClassName);
                            pw.println(tabs + nativeClassName + "::~" + nativeClassName + "() {");
                            pw.println(tabs + TAB_STRING + "if(NULL != context) {");
                            pw.println(tabs + TAB_STRING + TAB_STRING + "deleteContext();");
                            pw.println(tabs + TAB_STRING + "}");
                            pw.println(tabs + TAB_STRING + "context=NULL;");
                            pw.println(tabs + "}");
                            pw.println();
                            //                            pw.println(tabs + "public:");
                            //                            pw.println(tabs + nativeClassName + "();");
                            //                            pw.println(tabs + "~" + nativeClassName + "();");
                            tabs = tabs.substring(TAB_STRING.length());
                            //                            Method methods[] = javaClass.getDeclaredMethods();
                            //                            for (int j = 0; j < methods.length; j++) {
                            //                                Method method = methods[j];
                            //                                int modifiers = method.getModifiers();
                            //                                if (!Modifier.isPublic(modifiers)) {
                            //                                    continue;
                            //                                }
                            //                                pw.println(tabs + getCppDeclaration(method, javaClass));
                            //                            }
                            //                            pw.println(tabs + "}; // end class " + nativeClassName);
                            processTemplate(pw, map, CPP_END_CLASSCPP, tabs);
                        }
                    }
                    processTemplate(pw, map, "cpp_template_end_first.cpp", tabs);
                    tabs = "";
                    if (null != nativesClassMap) {
                        pw.println("using namespace " + namespace + ";");
                        pw.println("#ifdef __cplusplus");
                        pw.println("extern \"C\" {");
                        pw.println("#endif");
                        int max_method_count = 0;
                        tabs = "";
                        for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                            final Class javaClass = e.getValue();
                            final String nativeClassName = e.getKey();
                            map.put(CLASS_NAME, nativeClassName);
                            map.put(FULL_CLASS_NAME, nativeClassName);
                            map.put("%BASE_CLASS_FULL_NAME%",
                                    "::" + namespace + "::" + getModifiedClassName(javaClass).replace(".", "::"));
                            map.put(OBJECT_CLASS_FULL_NAME, "::" + namespace + "::java::lang::Object");
                            map.put(FULL_CLASS_JNI_SIGNATURE, nativeClassName);
                            map.put(METHOD_ONFAIL, "return;");
                            Method methods[] = javaClass.getDeclaredMethods();
                            if (max_method_count < methods.length) {
                                max_method_count = methods.length;
                            }
                            for (int j = 0; j < methods.length; j++) {
                                Method method = methods[j];
                                int modifiers = method.getModifiers();
                                if (!Modifier.isPublic(modifiers)) {
                                    continue;
                                }
                                if (Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers)
                                        && !Modifier.isStatic(modifiers)
                                        //                                        && !method.isDefault()
                                        && !method.isSynthetic()) {
                                    Class[] paramClasses = method.getParameterTypes();
                                    String methodArgs = getCppParamNames(paramClasses);
                                    map.put(METHOD_ARGS, methodArgs);
                                    map.put(METHOD_NAME, method.getName());
                                    Class returnClass = method.getReturnType();
                                    String retStore = isVoid(returnClass) ? ""
                                            : "retVal= (" + getMethodReturnVarType(returnClass) + ") ";
                                    map.put(METHOD_ONFAIL, getOnFailString(returnClass, javaClass));
                                    map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclare(returnClass));
                                    map.put("%METHOD_RETURN_STORE%", retStore);
                                    map.put("%METHOD_RETURN_GET%",
                                            getMethodReturnGet(tabs, returnClass, javaClass));
                                    pw.println();
                                    String paramDecls = getCppParamDeclarations(paramClasses, javaClass);
                                    String argsToAdd = method.getParameterTypes().length > 0
                                            ? "," + paramDecls.substring(1, paramDecls.length() - 1)
                                            : "";
                                    pw.println("JNIEXPORT " + getCppType(returnClass, javaClass) + " JNICALL Java_"
                                            + nativeClassName + "_" + method.getName()
                                            + "(JNIEnv *env, jobject jthis" + argsToAdd + ") {");
                                    tabs = TAB_STRING;
                                    processTemplate(pw, map, "cpp_native_wrap.cpp", tabs);
                                    tabs = tabs.substring(TAB_STRING.length());
                                    pw.println("}");
                                    pw.println();
                                }
                            }
                            pw.println("JNIEXPORT void JNICALL Java_" + nativeClassName
                                    + "_nativeDelete(JNIEnv *env, jobject jthis) {");
                            tabs += TAB_STRING;
                            map.put(METHOD_ONFAIL, getOnFailString(void.class, javaClass));
                            processTemplate(pw, map, "cpp_native_delete.cpp", tabs);
                            tabs = tabs.substring(TAB_STRING.length());
                            pw.println(tabs + "}");
                            pw.println();
                        }
                        pw.println("#ifdef __cplusplus");
                        pw.println("} // end extern \"C\"");
                        pw.println("#endif");
                        map.put("%MAX_METHOD_COUNT%", Integer.toString(max_method_count + 1));
                        processTemplate(pw, map, "cpp_start_register_native.cpp", tabs);
                        for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                            final Class javaClass = e.getValue();
                            final String nativeClassName = e.getKey();
                            map.put(CLASS_NAME, nativeClassName);
                            map.put(FULL_CLASS_NAME, nativeClassName);
                            map.put("%BASE_CLASS_FULL_NAME%",
                                    "::" + namespace + "::" + getModifiedClassName(javaClass).replace(".", "::"));
                            map.put(OBJECT_CLASS_FULL_NAME, "::" + namespace + "::java::lang::Object");
                            processTemplate(pw, map, "cpp_start_register_native_class.cpp", tabs);
                            tabs += TAB_STRING;
                            Method methods[] = javaClass.getDeclaredMethods();
                            int method_number = 0;
                            for (int j = 0; j < methods.length; j++) {
                                Method method = methods[j];
                                int modifiers = method.getModifiers();
                                if (!Modifier.isPublic(modifiers)) {
                                    continue;
                                }
                                if (Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers)
                                        && !Modifier.isStatic(modifiers)
                                        //                                        && !method.isDefault()
                                        && !method.isSynthetic()) {
                                    map.put("%METHOD_NUMBER%", Integer.toString(method_number));
                                    map.put(METHOD_NAME, method.getName());

                                    map.put(JNI_SIGNATURE, "(" + getJNIParamSignature(method.getParameterTypes())
                                            + ")" + classToJNISignature(method.getReturnType()));
                                    processTemplate(pw, map, "cpp_register_native_item.cpp", tabs);
                                    method_number++;
                                }
                            }
                            map.put("%METHOD_NUMBER%", Integer.toString(method_number));
                            map.put(METHOD_NAME, "nativeDelete");
                            map.put(JNI_SIGNATURE, "()V");
                            processTemplate(pw, map, "cpp_register_native_item.cpp", tabs);
                            map.put("%NUM_NATIVE_METHODS%", Integer.toString(method_number));
                            processTemplate(pw, map, "cpp_end_register_native_class.cpp", tabs);
                            tabs = tabs.substring(TAB_STRING.length());
                        }
                        processTemplate(pw, map, "cpp_end_register_native.cpp", tabs);
                    } else {
                        pw.println("// No Native classes : registerNativMethods not needed.");
                        pw.println("static void registerNativeMethods(JNIEnv *env) {}");
                    }

                } else {
                    processTemplate(pw, map, CPP_TEMPLATE_ENDCPP, tabs);
                }
            }
            if (null != nativesClassMap) {
                tabs = "";
                for (Entry<String, Class> e : nativesClassMap.entrySet()) {
                    String nativeClassName = e.getKey();
                    File nativeClassHeaderFile = new File(
                            namespace.toLowerCase() + "_" + nativeClassName.toLowerCase() + ".h");
                    map.put("%NATIVE_CLASS_HEADER%", nativeClassHeaderFile.getName());
                    map.put(CLASS_NAME, nativeClassName);
                    if (nativeClassHeaderFile.exists()) {
                        if (verbose) {
                            System.out.println("skipping " + nativeClassHeaderFile.getCanonicalPath()
                                    + " since it already exists.");
                        }
                    } else {
                        try (PrintWriter pw = new PrintWriter(new FileWriter(nativeClassHeaderFile))) {
                            processTemplate(pw, map, "header_native_imp.h", tabs);
                        }
                    }
                    File nativeClassCppFile = new File(
                            namespace.toLowerCase() + "_" + nativeClassName.toLowerCase() + ".cpp");
                    if (nativeClassCppFile.exists()) {
                        if (verbose) {
                            System.out.println("skipping " + nativeClassCppFile.getCanonicalPath()
                                    + " since it already exists.");
                        }
                    } else {
                        try (PrintWriter pw = new PrintWriter(new FileWriter(nativeClassCppFile))) {
                            processTemplate(pw, map, "cpp_native_imp_start.cpp", tabs);
                            Class javaClass = e.getValue();
                            Method methods[] = javaClass.getDeclaredMethods();
                            int method_number = 0;
                            for (int j = 0; j < methods.length; j++) {
                                Method method = methods[j];
                                int modifiers = method.getModifiers();
                                if (!Modifier.isPublic(modifiers)) {
                                    continue;
                                }
                                if (Modifier.isAbstract(modifiers) && Modifier.isPublic(modifiers)
                                        && !Modifier.isStatic(modifiers)
                                        //                                        && !method.isDefault()
                                        && !method.isSynthetic()) {
                                    Class[] paramClasses = method.getParameterTypes();
                                    //                                    String methodArgs = getCppParamNames(paramClasses);
                                    String paramDecls = getCppParamDeclarations(paramClasses, javaClass);
                                    String methodArgs = method.getParameterTypes().length > 0
                                            ? paramDecls.substring(1, paramDecls.length() - 1)
                                            : "";
                                    map.put(METHOD_ARGS, methodArgs);
                                    map.put(METHOD_NAME, method.getName());
                                    Class returnClass = method.getReturnType();
                                    String retStore = isVoid(returnClass) ? ""
                                            : "retVal= (" + getMethodReturnVarType(returnClass) + ") ";
                                    map.put(METHOD_ONFAIL, getOnFailString(returnClass, javaClass));
                                    map.put("%RETURN_TYPE%", getCppType(returnClass, javaClass));
                                    map.put("%RETURN_VAR_DECLARE%", getMethodReturnVarDeclare(returnClass));
                                    map.put("%METHOD_RETURN_STORE%", retStore);
                                    map.put("%METHOD_RETURN_GET%",
                                            getMethodReturnGet(tabs, returnClass, javaClass));
                                    processTemplate(pw, map, "cpp_native_imp_stub.cpp", tabs);
                                }
                            }
                            processTemplate(pw, map, "cpp_native_imp_end.cpp", tabs);
                        }
                    }
                }
            }
        }

        main_completed = true;
    }

    private static final String CLASSESPEROUTPUT = "classes-per-output";

    private static boolean checkConstructor(Constructor c, Class clss, List<Class> classes) {
        if (!Modifier.isPublic(c.getModifiers())) {
            if (c.getParameterTypes().length != 0 || !Modifier.isProtected(c.getModifiers())) {
                return true;
            }
        }
        Constructor ca[] = clss.getDeclaredConstructors();
        for (int i = 0; i < ca.length; i++) {
            Constructor constructor = ca[i];
            if (constructor.equals(c)) {
                break;
            }
            if (constructor.getParameterTypes().length == c.getParameterTypes().length) {
                if (c.getParameterTypes().length >= 1
                        && String.class.isAssignableFrom(c.getParameterTypes()[0]) != String.class
                                .isAssignableFrom(constructor.getParameterTypes()[0])) {
                    continue;
                }
                return true;
            }
        }
        if (c.getParameterTypes().length == 1 && clss.isAssignableFrom(c.getParameterTypes()[0])) {
            return true;
        }
        if (!checkParameters(c.getParameterTypes(), classes)) {
            return true;
        }
        return false;
    }

    private static final int DEFAULT_LIMIT = 200;
    private static final String TAB_STRING = "    "; // used to be "\t"
    private static final String OBJECT_CLASS_FULL_NAME = "%OBJECT_CLASS_FULL_NAME%";

    private static String getMethodReturnGet(String tabs, Class returnClass, Class relClass) {

        if (!returnClass.isArray() && !returnClass.isPrimitive() && !isString(returnClass)) {
            tabs += TAB_STRING;
            return tabs + "\n" + tabs + "jobjectRefType ref = env->GetObjectRefType(retVal);\n" + tabs
                    + "if(GetDebugJ4Cpp()) DebugPrintJObject(__FILE__,__LINE__,\"retVal=\",retVal);"
                    //+ tabs + "std::cout << \"ref=\" << ref << std::endl;\n"
                    //                    + tabs + "std::cout << \"retVal=\" << retVal << std::endl;\n"
                    //                    + tabs + "if(ref != JNIGlobalRefType) {\n" 
                    //                    + tabs + "\tretVal = env->NewGlobalRef(retVal);\n"
                    //                    + tabs + "}\n"
                    //                    + tabs + "\n"
                    //                    + tabs + "ref = env->GetObjectRefType(retVal);\n"
                    //                    + tabs + "std::cout << \"ref=\" << ref << std::endl;\n"
                    //                    + tabs + "std::cout << \"retVal=\" << retVal << std::endl;\n"
                    + tabs + getCppRelativeName(returnClass, relClass) + " retObject(retVal,false);\n" + tabs
                    + "return retObject;";
        }
        return isVoid(returnClass) ? "" : "return retVal;";
    }

    private static String classToCppBase(Class clss) {
        Class superClass = clss.getSuperclass();
        if (superClass == null) {
            superClass = Object.class;
        }
        return getCppRelativeName(superClass, clss);
    }

    private static final String METHOD_RETURN_GET = "%METHOD_RETURN_GET%";
    private static final String METHOD_RETURN_STORE = "%METHOD_RETURN_STORE%";
    private static final String CPP_EASY_STRING_ARRAY_METHODCPP = "cpp_easy_string_array_method.cpp";
    private static final String HEADER_CLASS_STARTH = "header_class_start.h";
    private static final String CPP_STATIC_METHODCPP = "cpp_static_method.cpp";
    private static final String JAR = "%JAR%";
    private static final String FULL_CLASS_JNI_SIGNATURE = "%FULL_CLASS_JNI_SIGNATURE%";
    private static final String METHOD_ONFAIL = "%METHOD_ONFAIL%";
    private static final String METHOD_ARGS = "%METHOD_ARGS%";
    private static final String METHOD_NAME = "%METHOD_NAME%";
    private static final String CONSTRUCTOR_ARGS = "%CONSTRUCTOR_ARGS%";
    private static final String JNI_SIGNATURE = "%JNI_SIGNATURE%";
    private static final String FULL_CLASS_NAME = "%FULL_CLASS_NAME%";
    private static final String CLASS_NAME = "%CLASS_NAME%";
    private static final String NAMESPACE = "%NAMESPACE%";
    private static final String HEADER_DEFINE = "%HEADER_DEFINE%";
    private static final String HEADER = "%HEADER%";

    private static boolean pkgMatch(String pkgs1[], String pkgs2[], int level) {
        if (pkgs1.length < level + 1) {
            return false;
        }
        if (pkgs2.length < level + 1) {
            return false;
        }
        for (int i = 0; i <= level; i++) {
            String pkgFrom1 = pkgs1[i];
            String pkgFrom2 = pkgs2[i];
            if (!pkgFrom1.equals(pkgFrom2)) {
                return false;
            }
        }
        return true;
    }

    /**
     *
     * @param clss the value of clss
     * @param pw the value of pw
     * @param tabs the value of tabs
     * @param nextClass the value of nextClass
     */
    private static String closeClassNamespace(Class clss, final PrintWriter pw, String tabs, Class nextClass) {
        String pkgs[] = classToPackages(clss);
        String nextPkgs[] = classToPackages(nextClass);
        boolean line_printed = false;
        if (pkgs.length < 1) {
            return tabs;
        }
        for (int i = pkgs.length - 1; i >= 0; i--) {
            String pkg = pkgs[i];
            if (pkg.length() < 1) {
                break;
            }
            if (!pkgMatch(pkgs, nextPkgs, i)) {
                pw.println(tabs + "} // end namespace " + pkg);
                line_printed = true;
            }
            tabs = tabs.substring(0, tabs.length() - 1);
        }
        if (line_printed) {
            pw.println();
        }
        return tabs;
    }

    private static final String[] emptypkgs = {};

    private static String[] classToPackages(Class clss) {
        if (null != clss && null != clss.getPackage() && null != clss.getPackage().getName()) {
            return clss.getPackage().getName().split("\\.");
        }
        return emptypkgs;
        //        return Optional.ofNullable(clss)
        //                .map(Class::getPackage)
        //                .map(Package::getName)
        //                .map(s -> s.split("\\."))
        //                .orElse(emptypkgs);
    }

    /**
     *
     * @param clss the value of clss
     * @param pw the value of pw
     * @param tabs the value of tabs
     * @param lastClass the value of lastClass
     */
    private static String openClassNamespace(Class clss, final PrintWriter pw, String tabs, Class lastClass) {

        String pkgs[] = classToPackages(clss);
        String lastpkgs[] = classToPackages(lastClass);
        for (int i = 0; i < pkgs.length; i++) {
            String pkg = pkgs[i];
            if (pkgMatch(pkgs, lastpkgs, i)) {
                continue;
            }
            tabs += TAB_STRING;
            pw.println(tabs + "namespace " + pkg + "{");
        }
        return tabs;
    }

    /**
     *
     * @param pw the value of pw
     * @param map the value of map
     * @param resourceName the value of resourceName
     * @param tabs the value of tabs
     * @throws IOException
     */
    private static void processTemplate(final PrintWriter pw, Map<String, String> map, String resourceName,
            String tabs) throws IOException {
        //                String arg = args[i];

        try (BufferedReader br = new BufferedReader(new InputStreamReader(
                ClassLoader.getSystemResourceAsStream(resourceName), StandardCharsets.UTF_8))) {

            String s = null;
            while (null != (s = br.readLine())) {
                if (!s.trim().startsWith(TEMPLATE_COMMENT_MARK)) {
                    s = s.replace("\t", TAB_STRING);
                    pw.println(tabs + replaceVars(map, s));
                }
            }
            //                    .filter(s -> !s.trim().startsWith(TEMPLATE_COMMENT_MARK))
            //                    .map(l -> l.replace("\t", TAB_STRING))
            //                    .forEach(l -> pw.println(tabs + replaceVars(map, l)));
        }
    }

    private static final String TEMPLATE_COMMENT_MARK = "//%%%";
    private static final String HEADER_TEMPLATE_STARTH = "header_template_start.h";
    private static final String HEADER_TEMPLATE_ENDH = "header_template_end.h";
    private static final String CPP_TEMPLATE_STARTCPP = "cpp_template_start.cpp";
    private static final String CPP_TEMPLATE_ENDCPP = "cpp_template_end.cpp";
    private static final String CPP_START_CLASSCPP = "cpp_start_class.cpp";
    private static final String CPP_END_CLASSCPP = "cpp_end_class.cpp";
    private static final String CPP_NEWCPP = "cpp_new.cpp";
    private static final String CPP_METHODCPP = "cpp_method.cpp";

    private static void printHelpAndExit(Options options, String args[]) {
        System.out.println("args = " + Arrays.toString(args));
        new HelpFormatter().printHelp("java4cpp", options);
        System.exit(1);
    }
}