<commit_before>/**
 *  Copyright (C) 2010 Cloud.com, Inc.  All rights reserved.
 * 
 * This software is licensed under the GNU General Public License v3 or later.
 * 
 * It is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package com.cloud.api.doc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.log4j.Logger;

import com.cloud.alert.AlertManager;
import com.cloud.api.BaseAsyncCmd;
import com.cloud.api.BaseAsyncCreateCmd;
import com.cloud.api.BaseCmd;
import com.cloud.api.Implementation;
import com.cloud.api.Parameter;
import com.cloud.api.response.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import com.thoughtworks.xstream.XStream;

public class ApiXmlDocWriter {
    public static final Logger s_logger = Logger.getLogger(ApiXmlDocWriter.class.getName());

    private static final short DOMAIN_ADMIN_COMMAND = 2;
    private static final short USER_COMMAND = 8;
    private static LinkedHashMap<Object, String> all_api_commands = new LinkedHashMap<Object, String>();
    private static LinkedHashMap<Object, String> domain_admin_api_commands = new LinkedHashMap<Object, String>();
    private static LinkedHashMap<Object, String> regular_user_api_commands = new LinkedHashMap<Object, String>();
    private static TreeMap<Object, String> all_api_commands_sorted = new TreeMap<Object, String>();
    private static TreeMap<Object, String> domain_admin_api_commands_sorted = new TreeMap<Object, String>();
    private static TreeMap<Object, String> regular_user_api_commands_sorted = new TreeMap<Object, String>();
    private static String dirName = "";

    public static void main(String[] args) {
        LinkedProperties preProcessedCommands = new LinkedProperties();
        String[] fileNames = null;

        List<String> argsList = Arrays.asList(args);
        Iterator<String> iter = argsList.iterator();
        while (iter.hasNext()) {
            String arg = iter.next();
            // populate the file names
            if (arg.equals("-f")) {
                fileNames = iter.next().split(",");
            }
            if (arg.equals("-d")) {
                dirName = iter.next();
            }
        }

        if ((fileNames == null) || (fileNames.length == 0)) {
            System.out.println("Please specify input file(s) separated by coma using -f option");
            System.exit(2);
        }

        for (String fileName : fileNames) {
            try {
                FileInputStream in = new FileInputStream(fileName);
                preProcessedCommands.load(in);
            } catch (FileNotFoundException ex) {
                System.out.println("Can't find file " + fileName);
                System.exit(2);
            } catch (IOException ex1) {
                System.out.println("Error reading from file " + ex1);
                System.exit(2);
            }
        }

        Iterator<?> propertiesIterator = preProcessedCommands.keys.iterator();
        // Get command classes and response object classes
        while (propertiesIterator.hasNext()) {
            String key = (String) propertiesIterator.next();
            String preProcessedCommand = preProcessedCommands.getProperty(key);
            String[] commandParts = preProcessedCommand.split(";");
            String commandName = commandParts[0];
            all_api_commands.put(key, commandName);

            short cmdPermissions = 1;
            if (commandParts.length > 1 && commandParts[1] != null) {
                cmdPermissions = Short.parseShort(commandParts[1]);
            }

            if ((cmdPermissions & DOMAIN_ADMIN_COMMAND) != 0) {
                domain_admin_api_commands.put(key, commandName);
            }
            if ((cmdPermissions & USER_COMMAND) != 0) {
                regular_user_api_commands.put(key, commandName);
            }
        }

        // Login and logout commands are hardcoded
        all_api_commands.put("login", "login");
        domain_admin_api_commands.put("login", "login");
        regular_user_api_commands.put("login", "login");

        all_api_commands.put("logout", "logout");
        domain_admin_api_commands.put("logout", "logout");
        regular_user_api_commands.put("logout", "logout");

        all_api_commands_sorted.putAll(all_api_commands);
        domain_admin_api_commands_sorted.putAll(domain_admin_api_commands);
        regular_user_api_commands_sorted.putAll(regular_user_api_commands);

        try {
            // Create object writer
            XStream xs = new XStream();
            xs.alias("command", Command.class);
            xs.alias("arg", Argument.class);
            String xmlDocDir = dirName + "/xmldoc";
            String rootAdminDirName = xmlDocDir + "/root_admin";
            String domainAdminDirName = xmlDocDir + "/domain_admin";
            String regularUserDirName = xmlDocDir + "/regular_user";
            (new File(rootAdminDirName)).mkdirs();
            (new File(domainAdminDirName)).mkdirs();
            (new File(regularUserDirName)).mkdirs();

            ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/commands.xml"), "commands");
            ObjectOutputStream rootAdmin = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummary.xml"), "commands");
            ObjectOutputStream rootAdminSorted = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummarySorted.xml"), "commands");
            ObjectOutputStream domainAdmin = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummary.xml"), "commands");
            ObjectOutputStream outDomainAdminSorted = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummarySorted.xml"), "commands");
            ObjectOutputStream regularUser = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummary.xml"), "commands");
            ObjectOutputStream regularUserSorted = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummarySorted.xml"), "commands");

            // Write commands in the order they are represented in commands.properties.in file
            Iterator<?> it = all_api_commands.keySet().iterator();
            while (it.hasNext()) {
                String key = (String) it.next();

                // Write admin commands
                if (key.equals("login")) {
                    writeLoginCommand(out);
                    writeLoginCommand(rootAdmin);
                    writeLoginCommand(domainAdmin);
                    writeLoginCommand(regularUser);

                    ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleRootAdminCommandOs);
                    singleRootAdminCommandOs.close();

                    ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleDomainAdminCommandOs);
                    singleDomainAdminCommandOs.close();

                    ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleRegularUserCommandOs);
                    singleRegularUserCommandOs.close();

                } else if (key.equals("logout")) {
                    writeLogoutCommand(out);
                    writeLogoutCommand(rootAdmin);
                    writeLogoutCommand(domainAdmin);
                    writeLogoutCommand(regularUser);

                    ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleRootAdminCommandOs);
                    singleRootAdminCommandOs.close();

                    ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleDomainAdminCommandOs);
                    singleDomainAdminCommandOs.close();

                    ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleRegularUserCommandOs);
                    singleRegularUserCommandOs.close();

                } else {
                    writeCommand(out, key);
                    writeCommand(rootAdmin, key);

                    // Write single commands to separate xml files
                    if (!key.equals("login")) {
                        ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleRootAdminCommandOs, key);
                        singleRootAdminCommandOs.close();
                    }

                    if (domain_admin_api_commands.containsKey(key)) {
                        writeCommand(domainAdmin, key);
                        ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleDomainAdminCommandOs, key);
                        singleDomainAdminCommandOs.close();
                    }

                    if (regular_user_api_commands.containsKey(key)) {
                        writeCommand(regularUser, key);
                        ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleRegularUserCommandOs, key);
                        singleRegularUserCommandOs.close();
                    }
                }
            }

            // Write sorted commands
            it = all_api_commands_sorted.keySet().iterator();
            while (it.hasNext()) {
                String key = (String) it.next();

                if (key.equals("login")) {
                    writeLoginCommand(rootAdminSorted);
                    writeLoginCommand(outDomainAdminSorted);
                    writeLoginCommand(regularUserSorted);
                } else if (key.equals("logout")) {
                    writeLogoutCommand(rootAdminSorted);
                    writeLogoutCommand(outDomainAdminSorted);
                    writeLogoutCommand(regularUserSorted);
                } else {
                    writeCommand(rootAdminSorted, key);

                    if (domain_admin_api_commands.containsKey(key)) {
                        writeCommand(outDomainAdminSorted, key);
                    }

                    if (regular_user_api_commands.containsKey(key)) {
                        writeCommand(regularUserSorted, key);
                    }
                }
            }

            out.close();
            rootAdmin.close();
            rootAdminSorted.close();
            domainAdmin.close();
            outDomainAdminSorted.close();
            regularUser.close();
            regularUserSorted.close();

            // write alerttypes to xml
            writeAlertTypes(xmlDocDir);

            // gzip directory with xml doc
            // zipDir(dirName + "xmldoc.zip", xmlDocDir);

            // Delete directory
            // deleteDir(new File(xmlDocDir));

        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(2);
        }
    }

    private static void writeCommand(ObjectOutputStream out, String command) throws ClassNotFoundException, IOException {
        Class<?> clas = Class.forName(all_api_commands.get(command));
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName(command);

        Implementation impl = clas.getAnnotation(Implementation.class);
        if (impl == null) {
            impl = clas.getSuperclass().getAnnotation(Implementation.class);
        }

        if (impl.includeInApiDoc()) {
            String commandDescription = impl.description();
            if (commandDescription != null)
                apiCommand.setDescription(commandDescription);
            else
                System.out.println("Command " + apiCommand.getName() + " misses description");

            // Set request parameters
            Field[] fields = clas.getDeclaredFields();

            // Get fields from superclass
            Class<?> superClass = clas.getSuperclass();
            String superName = superClass.getName();
            if (!superName.equals(BaseCmd.class.getName()) && !superName.equals(BaseAsyncCmd.class.getName()) && !superName.equals(BaseAsyncCreateCmd.class.getName())) {
                Field[] superClassFields = superClass.getDeclaredFields();
                if (superClassFields != null) {
                    Field[] tmpFields = new Field[fields.length + superClassFields.length];
                    System.arraycopy(fields, 0, tmpFields, 0, fields.length);
                    System.arraycopy(superClassFields, 0, tmpFields, fields.length, superClassFields.length);
                    fields = tmpFields;
                }
                superClass = superClass.getSuperclass();
            }
            request = setRequestFields(fields);

            // Set Async information for the command
            if (superName.equals(BaseAsyncCmd.class.getName()) || superName.equals(BaseAsyncCreateCmd.class.getName())) {
                apiCommand.setAsync(true);
            } else {
                apiCommand.setAsync(false);
            }

            // Get response parameters
            Class<?> responseClas = impl.responseObject();
            Field[] responseFields = responseClas.getDeclaredFields();
            response = setResponseFields(responseFields);

            apiCommand.setRequest(request);
            apiCommand.setResponse(response);

            out.writeObject(apiCommand);
        } else {
            s_logger.debug("Command " + command + " is not exposed in api doc");
        }
    }

    private static void writeLoginCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException {
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName("login");
        apiCommand
                .setDescription("Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.");

        // Generate request
        request.add(new Argument("username", "Username", true));
        request.add(new Argument("password", "Password", true));
        request.add(new Argument("domain", "path of the domain that the user belongs to. Example: domain=/com/cloud/internal.  If no domain is passed in, the ROOT domain is assumed.", false));
        apiCommand.setRequest(request);

        // Generate response
        response.add(new Argument("username", "Username"));
        response.add(new Argument("userid", "User id"));
        response.add(new Argument("password", "Password"));
        response.add(new Argument("domainid", "domain ID that the user belongs to"));
        response.add(new Argument("timeout", "the time period before the session has expired"));
        response.add(new Argument("account", "the account name the user belongs to"));
        response.add(new Argument("firstname", "first name of the user"));
        response.add(new Argument("lastname", "last name of the user"));
        response.add(new Argument("type", "the account type (admin, domain-admin, read-only-admin, user)"));
        response.add(new Argument("timezone", "user time zone"));
        response.add(new Argument("timezoneoffset", "user time zone offset from UTC 00:00"));
        response.add(new Argument("sessionkey", "Session key that can be passed in subsequent Query command calls"));
        apiCommand.setResponse(response);

        out.writeObject(apiCommand);
    }

    private static void writeLogoutCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException {
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName("logout");
        apiCommand.setDescription("Logs out the user");

        // Generate request - no request parameters
        apiCommand.setRequest(request);

        // Generate response
        response.add(new Argument("success", "success if the logout action succeeded"));
        apiCommand.setResponse(response);

        out.writeObject(apiCommand);
    }

    private static ArrayList<Argument> setRequestFields(Field[] fields) {
        ArrayList<Argument> arguments = new ArrayList<Argument>();
        ArrayList<Argument> requiredArguments = new ArrayList<Argument>();
        ArrayList<Argument> optionalArguments = new ArrayList<Argument>();
        Argument id = null;
        for (Field f : fields) {
            Parameter parameterAnnotation = f.getAnnotation(Parameter.class);
            if (parameterAnnotation != null && parameterAnnotation.expose() && parameterAnnotation.includeInApiDoc()) {
                Argument reqArg = new Argument(parameterAnnotation.name());
                reqArg.setRequired(parameterAnnotation.required());
                if (!parameterAnnotation.description().isEmpty()) {
                    reqArg.setDescription(parameterAnnotation.description());
                }

                if (reqArg.isRequired() == true) {
                    if (parameterAnnotation.name().equals("id")) {
                        id = reqArg;
                    } else {
                        requiredArguments.add(reqArg);
                    }
                } else {
                    optionalArguments.add(reqArg);
                }
            }
        }

        Collections.sort(requiredArguments);
        Collections.sort(optionalArguments);

        // sort required and optional arguments here
        if (id != null) {
            arguments.add(id);
        }
        arguments.addAll(requiredArguments);
        arguments.addAll(optionalArguments);

        return arguments;
    }

    private static ArrayList<Argument> setResponseFields(Field[] responseFields) {
        ArrayList<Argument> arguments = new ArrayList<Argument>();
        ArrayList<Argument> sortedChildlessArguments = new ArrayList<Argument>();
        ArrayList<Argument> sortedArguments = new ArrayList<Argument>();

        Argument id = null;

        for (Field responseField : responseFields) {
            SerializedName nameAnnotation = responseField.getAnnotation(SerializedName.class);
            Param paramAnnotation = responseField.getAnnotation(Param.class);
            Argument respArg = new Argument(nameAnnotation.value());

            boolean hasChildren = false;
            if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) {
                String description = paramAnnotation.description();
                Class fieldClass = paramAnnotation.responseObject();
                if (description != null && !description.isEmpty()) {
                    respArg.setDescription(description);
                }

                if (fieldClass != null) {
                    Class<?> superClass = fieldClass.getSuperclass();
                    if (superClass != null) {
                        String superName = superClass.getName();
                        if (superName.equals(BaseResponse.class.getName())) {
                            ArrayList<Argument> fieldArguments = new ArrayList<Argument>();
                            Field[] fields = fieldClass.getDeclaredFields();
                            fieldArguments = setResponseFields(fields);
                            respArg.setArguments(fieldArguments);
                            hasChildren = true;
                        }
                    }
                }
            }

            if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) {
                if (nameAnnotation.value().equals("id")) {
                    id = respArg;
                } else {
                    if (hasChildren) {
                        respArg.setName(nameAnnotation.value() + "(*)");
                        sortedArguments.add(respArg);
                    } else {
                        sortedChildlessArguments.add(respArg);
                    }
                }
            }
        }

        Collections.sort(sortedArguments);
        Collections.sort(sortedChildlessArguments);

        if (id != null) {
            arguments.add(id);
        }
        arguments.addAll(sortedChildlessArguments);
        arguments.addAll(sortedArguments);
        return arguments;
    }

    private static void zipDir(String zipFileName, String dir) throws Exception {
        File dirObj = new File(dir);
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
        addDir(dirObj, out);
        out.close();
    }

    static void addDir(File dirObj, ZipOutputStream out) throws IOException {
        File[] files = dirObj.listFiles();
        byte[] tmpBuf = new byte[1024];
        String pathToDir = dirName;

        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                addDir(files[i], out);
                continue;
            }
            FileInputStream in = new FileInputStream(files[i].getPath());
            out.putNextEntry(new ZipEntry(files[i].getPath().substring(pathToDir.length())));
            int len;
            while ((len = in.read(tmpBuf)) > 0) {
                out.write(tmpBuf, 0, len);
            }
            out.closeEntry();
            in.close();
        }
    }

    private static void deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                deleteDir(new File(dir, children[i]));
            }
        }
        dir.delete();
    }

    private static void writeAlertTypes(String dirName) {
        XStream xs = new XStream();
        xs.alias("alert", Alert.class);
        try {
            ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/alert_types.xml"), "alerts");
            for (Field f : AlertManager.class.getFields()) {
                String name = f.getName().substring(11);
                Alert alert = new Alert(name, f.getInt(null));
                out.writeObject(alert);
            }
            out.close();
        } catch (IOException e) {
            s_logger.error("Failed to create output stream to write an alert types ", e);
        } catch (IllegalAccessException e) {
            s_logger.error("Failed to read alert fields ", e);
        }
    }

    private static class LinkedProperties extends Properties {
        private final LinkedList<Object> keys = new LinkedList<Object>();

        @Override
        public Enumeration<Object> keys() {
            return Collections.<Object> enumeration(keys);
        }

        @Override
        public Object put(Object key, Object value) {
            // System.out.println("Adding key" + key);
            keys.add(key);
            return super.put(key, value);
        }
    }

}<commit_msg>bug 10088: Fixed apiXmlDoc generator to generate correct set of domain admin commands - got broken by checkin fff4112c0b2407e754318ea31f2f1bb7e92b8f12 "Introducing a new user role in cloudstack called RESOURCE_DOMAIN_ADMIN" status 10088: resolved fixed<commit_after>/**
 *  Copyright (C) 2010 Cloud.com, Inc.  All rights reserved.
 * 
 * This software is licensed under the GNU General Public License v3 or later.
 * 
 * It is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package com.cloud.api.doc;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Properties;
import java.util.TreeMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.log4j.Logger;

import com.cloud.alert.AlertManager;
import com.cloud.api.BaseAsyncCmd;
import com.cloud.api.BaseAsyncCreateCmd;
import com.cloud.api.BaseCmd;
import com.cloud.api.Implementation;
import com.cloud.api.Parameter;
import com.cloud.api.response.BaseResponse;
import com.cloud.serializer.Param;
import com.google.gson.annotations.SerializedName;
import com.thoughtworks.xstream.XStream;

public class ApiXmlDocWriter {
    public static final Logger s_logger = Logger.getLogger(ApiXmlDocWriter.class.getName());

    private static final short DOMAIN_ADMIN_COMMAND = 4;
    private static final short USER_COMMAND = 8;
    private static LinkedHashMap<Object, String> all_api_commands = new LinkedHashMap<Object, String>();
    private static LinkedHashMap<Object, String> domain_admin_api_commands = new LinkedHashMap<Object, String>();
    private static LinkedHashMap<Object, String> regular_user_api_commands = new LinkedHashMap<Object, String>();
    private static TreeMap<Object, String> all_api_commands_sorted = new TreeMap<Object, String>();
    private static TreeMap<Object, String> domain_admin_api_commands_sorted = new TreeMap<Object, String>();
    private static TreeMap<Object, String> regular_user_api_commands_sorted = new TreeMap<Object, String>();
    private static String dirName = "";

    public static void main(String[] args) {
        LinkedProperties preProcessedCommands = new LinkedProperties();
        String[] fileNames = null;

        List<String> argsList = Arrays.asList(args);
        Iterator<String> iter = argsList.iterator();
        while (iter.hasNext()) {
            String arg = iter.next();
            // populate the file names
            if (arg.equals("-f")) {
                fileNames = iter.next().split(",");
            }
            if (arg.equals("-d")) {
                dirName = iter.next();
            }
        }

        if ((fileNames == null) || (fileNames.length == 0)) {
            System.out.println("Please specify input file(s) separated by coma using -f option");
            System.exit(2);
        }

        for (String fileName : fileNames) {
            try {
                FileInputStream in = new FileInputStream(fileName);
                preProcessedCommands.load(in);
            } catch (FileNotFoundException ex) {
                System.out.println("Can't find file " + fileName);
                System.exit(2);
            } catch (IOException ex1) {
                System.out.println("Error reading from file " + ex1);
                System.exit(2);
            }
        }

        Iterator<?> propertiesIterator = preProcessedCommands.keys.iterator();
        // Get command classes and response object classes
        while (propertiesIterator.hasNext()) {
            String key = (String) propertiesIterator.next();
            String preProcessedCommand = preProcessedCommands.getProperty(key);
            String[] commandParts = preProcessedCommand.split(";");
            String commandName = commandParts[0];
            all_api_commands.put(key, commandName);

            short cmdPermissions = 1;
            if (commandParts.length > 1 && commandParts[1] != null) {
                cmdPermissions = Short.parseShort(commandParts[1]);
            }

            if ((cmdPermissions & DOMAIN_ADMIN_COMMAND) != 0) {
                domain_admin_api_commands.put(key, commandName);
            }
            if ((cmdPermissions & USER_COMMAND) != 0) {
                regular_user_api_commands.put(key, commandName);
            }
        }

        // Login and logout commands are hardcoded
        all_api_commands.put("login", "login");
        domain_admin_api_commands.put("login", "login");
        regular_user_api_commands.put("login", "login");

        all_api_commands.put("logout", "logout");
        domain_admin_api_commands.put("logout", "logout");
        regular_user_api_commands.put("logout", "logout");

        all_api_commands_sorted.putAll(all_api_commands);
        domain_admin_api_commands_sorted.putAll(domain_admin_api_commands);
        regular_user_api_commands_sorted.putAll(regular_user_api_commands);

        try {
            // Create object writer
            XStream xs = new XStream();
            xs.alias("command", Command.class);
            xs.alias("arg", Argument.class);
            String xmlDocDir = dirName + "/xmldoc";
            String rootAdminDirName = xmlDocDir + "/root_admin";
            String domainAdminDirName = xmlDocDir + "/domain_admin";
            String regularUserDirName = xmlDocDir + "/regular_user";
            (new File(rootAdminDirName)).mkdirs();
            (new File(domainAdminDirName)).mkdirs();
            (new File(regularUserDirName)).mkdirs();

            ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/commands.xml"), "commands");
            ObjectOutputStream rootAdmin = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummary.xml"), "commands");
            ObjectOutputStream rootAdminSorted = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "rootAdminSummarySorted.xml"), "commands");
            ObjectOutputStream domainAdmin = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummary.xml"), "commands");
            ObjectOutputStream outDomainAdminSorted = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "domainAdminSummarySorted.xml"), "commands");
            ObjectOutputStream regularUser = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummary.xml"), "commands");
            ObjectOutputStream regularUserSorted = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/regularUserSummarySorted.xml"), "commands");

            // Write commands in the order they are represented in commands.properties.in file
            Iterator<?> it = all_api_commands.keySet().iterator();
            while (it.hasNext()) {
                String key = (String) it.next();

                // Write admin commands
                if (key.equals("login")) {
                    writeLoginCommand(out);
                    writeLoginCommand(rootAdmin);
                    writeLoginCommand(domainAdmin);
                    writeLoginCommand(regularUser);

                    ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleRootAdminCommandOs);
                    singleRootAdminCommandOs.close();

                    ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleDomainAdminCommandOs);
                    singleDomainAdminCommandOs.close();

                    ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "login" + ".xml"), "command");
                    writeLoginCommand(singleRegularUserCommandOs);
                    singleRegularUserCommandOs.close();

                } else if (key.equals("logout")) {
                    writeLogoutCommand(out);
                    writeLogoutCommand(rootAdmin);
                    writeLogoutCommand(domainAdmin);
                    writeLogoutCommand(regularUser);

                    ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleRootAdminCommandOs);
                    singleRootAdminCommandOs.close();

                    ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleDomainAdminCommandOs);
                    singleDomainAdminCommandOs.close();

                    ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + "logout" + ".xml"), "command");
                    writeLogoutCommand(singleRegularUserCommandOs);
                    singleRegularUserCommandOs.close();

                } else {
                    writeCommand(out, key);
                    writeCommand(rootAdmin, key);

                    // Write single commands to separate xml files
                    if (!key.equals("login")) {
                        ObjectOutputStream singleRootAdminCommandOs = xs.createObjectOutputStream(new FileWriter(rootAdminDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleRootAdminCommandOs, key);
                        singleRootAdminCommandOs.close();
                    }

                    if (domain_admin_api_commands.containsKey(key)) {
                        writeCommand(domainAdmin, key);
                        ObjectOutputStream singleDomainAdminCommandOs = xs.createObjectOutputStream(new FileWriter(domainAdminDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleDomainAdminCommandOs, key);
                        singleDomainAdminCommandOs.close();
                    }

                    if (regular_user_api_commands.containsKey(key)) {
                        writeCommand(regularUser, key);
                        ObjectOutputStream singleRegularUserCommandOs = xs.createObjectOutputStream(new FileWriter(regularUserDirName + "/" + key + ".xml"), "command");
                        writeCommand(singleRegularUserCommandOs, key);
                        singleRegularUserCommandOs.close();
                    }
                }
            }

            // Write sorted commands
            it = all_api_commands_sorted.keySet().iterator();
            while (it.hasNext()) {
                String key = (String) it.next();

                if (key.equals("login")) {
                    writeLoginCommand(rootAdminSorted);
                    writeLoginCommand(outDomainAdminSorted);
                    writeLoginCommand(regularUserSorted);
                } else if (key.equals("logout")) {
                    writeLogoutCommand(rootAdminSorted);
                    writeLogoutCommand(outDomainAdminSorted);
                    writeLogoutCommand(regularUserSorted);
                } else {
                    writeCommand(rootAdminSorted, key);

                    if (domain_admin_api_commands.containsKey(key)) {
                        writeCommand(outDomainAdminSorted, key);
                    }

                    if (regular_user_api_commands.containsKey(key)) {
                        writeCommand(regularUserSorted, key);
                    }
                }
            }

            out.close();
            rootAdmin.close();
            rootAdminSorted.close();
            domainAdmin.close();
            outDomainAdminSorted.close();
            regularUser.close();
            regularUserSorted.close();

            // write alerttypes to xml
            writeAlertTypes(xmlDocDir);

            // gzip directory with xml doc
            // zipDir(dirName + "xmldoc.zip", xmlDocDir);

            // Delete directory
            // deleteDir(new File(xmlDocDir));

        } catch (Exception ex) {
            ex.printStackTrace();
            System.exit(2);
        }
    }

    private static void writeCommand(ObjectOutputStream out, String command) throws ClassNotFoundException, IOException {
        Class<?> clas = Class.forName(all_api_commands.get(command));
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName(command);

        Implementation impl = clas.getAnnotation(Implementation.class);
        if (impl == null) {
            impl = clas.getSuperclass().getAnnotation(Implementation.class);
        }

        if (impl.includeInApiDoc()) {
            String commandDescription = impl.description();
            if (commandDescription != null)
                apiCommand.setDescription(commandDescription);
            else
                System.out.println("Command " + apiCommand.getName() + " misses description");

            // Set request parameters
            Field[] fields = clas.getDeclaredFields();

            // Get fields from superclass
            Class<?> superClass = clas.getSuperclass();
            String superName = superClass.getName();
            if (!superName.equals(BaseCmd.class.getName()) && !superName.equals(BaseAsyncCmd.class.getName()) && !superName.equals(BaseAsyncCreateCmd.class.getName())) {
                Field[] superClassFields = superClass.getDeclaredFields();
                if (superClassFields != null) {
                    Field[] tmpFields = new Field[fields.length + superClassFields.length];
                    System.arraycopy(fields, 0, tmpFields, 0, fields.length);
                    System.arraycopy(superClassFields, 0, tmpFields, fields.length, superClassFields.length);
                    fields = tmpFields;
                }
                superClass = superClass.getSuperclass();
            }
            request = setRequestFields(fields);

            // Set Async information for the command
            if (superName.equals(BaseAsyncCmd.class.getName()) || superName.equals(BaseAsyncCreateCmd.class.getName())) {
                apiCommand.setAsync(true);
            } else {
                apiCommand.setAsync(false);
            }

            // Get response parameters
            Class<?> responseClas = impl.responseObject();
            Field[] responseFields = responseClas.getDeclaredFields();
            response = setResponseFields(responseFields);

            apiCommand.setRequest(request);
            apiCommand.setResponse(response);

            out.writeObject(apiCommand);
        } else {
            s_logger.debug("Command " + command + " is not exposed in api doc");
        }
    }

    private static void writeLoginCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException {
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName("login");
        apiCommand
                .setDescription("Logs a user into the CloudStack. A successful login attempt will generate a JSESSIONID cookie value that can be passed in subsequent Query command calls until the \"logout\" command has been issued or the session has expired.");

        // Generate request
        request.add(new Argument("username", "Username", true));
        request.add(new Argument("password", "Password", true));
        request.add(new Argument("domain", "path of the domain that the user belongs to. Example: domain=/com/cloud/internal.  If no domain is passed in, the ROOT domain is assumed.", false));
        apiCommand.setRequest(request);

        // Generate response
        response.add(new Argument("username", "Username"));
        response.add(new Argument("userid", "User id"));
        response.add(new Argument("password", "Password"));
        response.add(new Argument("domainid", "domain ID that the user belongs to"));
        response.add(new Argument("timeout", "the time period before the session has expired"));
        response.add(new Argument("account", "the account name the user belongs to"));
        response.add(new Argument("firstname", "first name of the user"));
        response.add(new Argument("lastname", "last name of the user"));
        response.add(new Argument("type", "the account type (admin, domain-admin, read-only-admin, user)"));
        response.add(new Argument("timezone", "user time zone"));
        response.add(new Argument("timezoneoffset", "user time zone offset from UTC 00:00"));
        response.add(new Argument("sessionkey", "Session key that can be passed in subsequent Query command calls"));
        apiCommand.setResponse(response);

        out.writeObject(apiCommand);
    }

    private static void writeLogoutCommand(ObjectOutputStream out) throws ClassNotFoundException, IOException {
        ArrayList<Argument> request = new ArrayList<Argument>();
        ArrayList<Argument> response = new ArrayList<Argument>();

        // Create a new command, set name and description
        Command apiCommand = new Command();
        apiCommand.setName("logout");
        apiCommand.setDescription("Logs out the user");

        // Generate request - no request parameters
        apiCommand.setRequest(request);

        // Generate response
        response.add(new Argument("success", "success if the logout action succeeded"));
        apiCommand.setResponse(response);

        out.writeObject(apiCommand);
    }

    private static ArrayList<Argument> setRequestFields(Field[] fields) {
        ArrayList<Argument> arguments = new ArrayList<Argument>();
        ArrayList<Argument> requiredArguments = new ArrayList<Argument>();
        ArrayList<Argument> optionalArguments = new ArrayList<Argument>();
        Argument id = null;
        for (Field f : fields) {
            Parameter parameterAnnotation = f.getAnnotation(Parameter.class);
            if (parameterAnnotation != null && parameterAnnotation.expose() && parameterAnnotation.includeInApiDoc()) {
                Argument reqArg = new Argument(parameterAnnotation.name());
                reqArg.setRequired(parameterAnnotation.required());
                if (!parameterAnnotation.description().isEmpty()) {
                    reqArg.setDescription(parameterAnnotation.description());
                }

                if (reqArg.isRequired() == true) {
                    if (parameterAnnotation.name().equals("id")) {
                        id = reqArg;
                    } else {
                        requiredArguments.add(reqArg);
                    }
                } else {
                    optionalArguments.add(reqArg);
                }
            }
        }

        Collections.sort(requiredArguments);
        Collections.sort(optionalArguments);

        // sort required and optional arguments here
        if (id != null) {
            arguments.add(id);
        }
        arguments.addAll(requiredArguments);
        arguments.addAll(optionalArguments);

        return arguments;
    }

    private static ArrayList<Argument> setResponseFields(Field[] responseFields) {
        ArrayList<Argument> arguments = new ArrayList<Argument>();
        ArrayList<Argument> sortedChildlessArguments = new ArrayList<Argument>();
        ArrayList<Argument> sortedArguments = new ArrayList<Argument>();

        Argument id = null;

        for (Field responseField : responseFields) {
            SerializedName nameAnnotation = responseField.getAnnotation(SerializedName.class);
            Param paramAnnotation = responseField.getAnnotation(Param.class);
            Argument respArg = new Argument(nameAnnotation.value());

            boolean hasChildren = false;
            if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) {
                String description = paramAnnotation.description();
                Class fieldClass = paramAnnotation.responseObject();
                if (description != null && !description.isEmpty()) {
                    respArg.setDescription(description);
                }

                if (fieldClass != null) {
                    Class<?> superClass = fieldClass.getSuperclass();
                    if (superClass != null) {
                        String superName = superClass.getName();
                        if (superName.equals(BaseResponse.class.getName())) {
                            ArrayList<Argument> fieldArguments = new ArrayList<Argument>();
                            Field[] fields = fieldClass.getDeclaredFields();
                            fieldArguments = setResponseFields(fields);
                            respArg.setArguments(fieldArguments);
                            hasChildren = true;
                        }
                    }
                }
            }

            if (paramAnnotation != null && paramAnnotation.includeInApiDoc()) {
                if (nameAnnotation.value().equals("id")) {
                    id = respArg;
                } else {
                    if (hasChildren) {
                        respArg.setName(nameAnnotation.value() + "(*)");
                        sortedArguments.add(respArg);
                    } else {
                        sortedChildlessArguments.add(respArg);
                    }
                }
            }
        }

        Collections.sort(sortedArguments);
        Collections.sort(sortedChildlessArguments);

        if (id != null) {
            arguments.add(id);
        }
        arguments.addAll(sortedChildlessArguments);
        arguments.addAll(sortedArguments);
        return arguments;
    }

    private static void zipDir(String zipFileName, String dir) throws Exception {
        File dirObj = new File(dir);
        ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFileName));
        addDir(dirObj, out);
        out.close();
    }

    static void addDir(File dirObj, ZipOutputStream out) throws IOException {
        File[] files = dirObj.listFiles();
        byte[] tmpBuf = new byte[1024];
        String pathToDir = dirName;

        for (int i = 0; i < files.length; i++) {
            if (files[i].isDirectory()) {
                addDir(files[i], out);
                continue;
            }
            FileInputStream in = new FileInputStream(files[i].getPath());
            out.putNextEntry(new ZipEntry(files[i].getPath().substring(pathToDir.length())));
            int len;
            while ((len = in.read(tmpBuf)) > 0) {
                out.write(tmpBuf, 0, len);
            }
            out.closeEntry();
            in.close();
        }
    }

    private static void deleteDir(File dir) {
        if (dir.isDirectory()) {
            String[] children = dir.list();
            for (int i = 0; i < children.length; i++) {
                deleteDir(new File(dir, children[i]));
            }
        }
        dir.delete();
    }

    private static void writeAlertTypes(String dirName) {
        XStream xs = new XStream();
        xs.alias("alert", Alert.class);
        try {
            ObjectOutputStream out = xs.createObjectOutputStream(new FileWriter(dirName + "/alert_types.xml"), "alerts");
            for (Field f : AlertManager.class.getFields()) {
                String name = f.getName().substring(11);
                Alert alert = new Alert(name, f.getInt(null));
                out.writeObject(alert);
            }
            out.close();
        } catch (IOException e) {
            s_logger.error("Failed to create output stream to write an alert types ", e);
        } catch (IllegalAccessException e) {
            s_logger.error("Failed to read alert fields ", e);
        }
    }

    private static class LinkedProperties extends Properties {
        private final LinkedList<Object> keys = new LinkedList<Object>();

        @Override
        public Enumeration<Object> keys() {
            return Collections.<Object> enumeration(keys);
        }

        @Override
        public Object put(Object key, Object value) {
            // System.out.println("Adding key" + key);
            keys.add(key);
            return super.put(key, value);
        }
    }

}
{'fraction_non_alphanumeric': 0.10119082767444149, 'fraction_numerical': 0.002263556736541679, 'mean_word_length': 2.238732708612227, 'pattern_counts': {'":': 0, '<': 113, '<?xml version=': 0, '>': 113, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 58}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3864440', 'n_tokens_mistral': 12425, 'n_tokens_neox': 11875, 'n_words': 2856}