/*
 * Copyright (c) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code 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
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package nsk.share.jdi;

import java.io.*;
import java.util.*;
import nsk.share.Log;
import nsk.share.ObjectInstancesManager;
import nsk.share.TestBug;
import nsk.share.jpda.DebugeeArgumentHandler;
import nsk.share.jpda.IOPipe;

/*
 * Debuggee class used in tests for heapwalking(tests for VirtualMachine.instanceCounts, ReferenceType.instances, ObjectReference.referrers).
 * Handle commands related to creation of objects instances with given reference type
 * and given referrers number, use for this purposes nsk.share.ObjectInstancesManager.
 */
public class HeapwalkingDebuggee extends AbstractJDIDebuggee {
    protected ObjectInstancesManager objectInstancesManager;

    // reference of this type should be included in ObjectReference.referringObjects
    public static Set<String> includedIntoReferrersCountTypes = new HashSet<String>();

    // reference of this type should be included in ReferenceType.instances
    public static Set<String> includedIntoInstancesCountTypes = new HashSet<String>();

    static {
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.STRONG_REFERENCE);
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.WEAK_REFERENCE);
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.SOFT_REFERENCE);
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.PHANTOM_REFERENCE);
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.JNI_GLOBAL_REFERENCE);
        includedIntoInstancesCountTypes.add(ObjectInstancesManager.JNI_LOCAL_REFERENCE);

        includedIntoReferrersCountTypes.add(ObjectInstancesManager.STRONG_REFERENCE);
        includedIntoReferrersCountTypes.add(ObjectInstancesManager.WEAK_REFERENCE);
        includedIntoReferrersCountTypes.add(ObjectInstancesManager.SOFT_REFERENCE);
        includedIntoReferrersCountTypes.add(ObjectInstancesManager.PHANTOM_REFERENCE);
    }

    //create number instance of class with given name, command format: createInstances:class_name:instance_count[:referrer_count:referrer_type]
    static public final String COMMAND_CREATE_INSTANCES = "createInstances";

    //'delete'(make unreachable) number instance of class with given name, command format: deleteInstances:class_name:instance_count:referrer_count
    static public final String COMMAND_DELETE_INSTANCES = "deleteInstances";

    //delete number referrers
    static public final String COMMAND_DELETE_REFERRERS = "deleteReferrers";

    //create instance with all type referrers
    static public final String COMMAND_CREATE_ALL_TYPE_REFERENCES = "createAllTypeReferences";

    protected void init(String args[]) {
        super.init(args);
        objectInstancesManager = new ObjectInstancesManager(log);
    }

    public void initDebuggee(DebugeeArgumentHandler argHandler, Log log, IOPipe pipe, String args[], boolean callExit) {
        super.initDebuggee(argHandler, log, pipe, args, callExit);
        objectInstancesManager = new ObjectInstancesManager(log);
    }

    public boolean parseCommand(String command) {
        if (super.parseCommand(command))
            return true;

        try {
            StreamTokenizer tokenizer = new StreamTokenizer(new StringReader(command));
            tokenizer.whitespaceChars(':', ':');
            tokenizer.wordChars('_', '_');
            tokenizer.wordChars('$', '$');
            tokenizer.wordChars('[', ']');
            tokenizer.wordChars('|', '|');

            if (command.startsWith(COMMAND_CREATE_INSTANCES)) {
                //createInstances:class_name:instance_count[:referrer_count:referrer_type]

                tokenizer.nextToken();

                if (tokenizer.nextToken() != StreamTokenizer.TT_WORD)
                    throw new TestBug("Invalid command format: " + command);

                String className = tokenizer.sval;

                if (tokenizer.nextToken() != StreamTokenizer.TT_NUMBER)
                    throw new TestBug("Invalid command format: " + command);

                int instanceCounts = (int) tokenizer.nval;

                int referrerCount = 1;
                Set<String> referrerType = new HashSet<String>();

                if (tokenizer.nextToken() == StreamTokenizer.TT_NUMBER) {
                    referrerCount = (int) tokenizer.nval;

                    if (tokenizer.nextToken() == StreamTokenizer.TT_WORD)
                        referrerType.addAll(Arrays.asList(tokenizer.sval.split("\\|")));
                }
                if (referrerType.isEmpty()) {
                    referrerType.add(ObjectInstancesManager.STRONG_REFERENCE);
                }

                objectInstancesManager.createReferences(instanceCounts, className, referrerCount, referrerType);

                return true;
            } else if (command.startsWith(COMMAND_DELETE_INSTANCES)) {
                //deleteInstances:class_name:instance_count:referrer_count

                tokenizer.nextToken();

                if (tokenizer.nextToken() != StreamTokenizer.TT_WORD)
                    throw new TestBug("Invalid command format: " + command);

                String className = tokenizer.sval;

                if (tokenizer.nextToken() != StreamTokenizer.TT_NUMBER)
                    throw new TestBug("Invalid command format: " + command);

                int instanceCounts = (int) tokenizer.nval;

                objectInstancesManager.deleteAllReferrers(instanceCounts, className);

                return true;
            } else if (command.startsWith(COMMAND_DELETE_REFERRERS)) {
                tokenizer.nextToken();

                if (tokenizer.nextToken() != StreamTokenizer.TT_WORD)
                    throw new TestBug("Invalid command format: " + command);

                String className = tokenizer.sval;

                if (tokenizer.nextToken() != StreamTokenizer.TT_NUMBER)
                    throw new TestBug("Invalid command format: " + command);

                int referrersCount = (int) tokenizer.nval;

                Set<String> referrerTypes = new HashSet<String>();
                if (tokenizer.nextToken() == StreamTokenizer.TT_WORD) {
                    referrerTypes.addAll(Arrays.asList(tokenizer.sval.split("\\|")));
                }

                objectInstancesManager.deleteReferrers(className, referrersCount, referrerTypes);

                return true;
            } else if (command.startsWith(COMMAND_CREATE_ALL_TYPE_REFERENCES)) {
                tokenizer.nextToken();

                if (tokenizer.nextToken() != StreamTokenizer.TT_WORD)
                    throw new TestBug("Invalid command format: " + command);

                String className = tokenizer.sval;

                if (tokenizer.nextToken() != StreamTokenizer.TT_NUMBER)
                    throw new TestBug("Invalid command format: " + command);

                int instanceCounts = (int) tokenizer.nval;

                objectInstancesManager.createAllTypeReferences(className, instanceCounts);

                return true;
            }
        } catch (IOException e) {
            throw new TestBug("Invalid command format: " + command);
        }

        return false;
    }

    // instances of some classes couldn't be strictly controlled during test execution, use non-strict checks for this classes
    public static boolean useStrictCheck(String className, boolean otherThreadPresent) {
        if (className.equals("java.lang.String"))
            return false;

        if (className.equals("char[]"))
            return false;

        if (className.equals("byte[]"))
            return false;

        if (className.equals("boolean[]"))
            return false;

        if (className.equals("float[]"))
            return false;

        if (className.equals("long[]"))
            return false;

        if (className.equals("int[]"))
            return false;

        if (className.equals("double[]"))
            return false;

        if (className.equals("java.lang.Thread")) {
            if (otherThreadPresent)
                return false;
        }

        return true;
    }

    // is reference with given type should be included in ObjectReference.referringObjects
    static public boolean isIncludedIntoReferrersCount(String referenceType) {
        if (!ObjectInstancesManager.allReferenceTypes.contains(referenceType)) {
            throw new TestBug("Invalid reference type: " + referenceType);
        }

        return includedIntoReferrersCountTypes.contains(referenceType);
    }

    // is reference with given type should be included in ReferenceType.instances
    static public boolean isIncludedIntoInstancesCount(String referenceType) {
        if (!ObjectInstancesManager.allReferenceTypes.contains(referenceType)) {
            throw new TestBug("Invalid reference type: " + referenceType);
        }

        return includedIntoInstancesCountTypes.contains(referenceType);
    }

    public static void main(String args[]) {
        HeapwalkingDebuggee debuggee = new HeapwalkingDebuggee();
        debuggee.init(args);
        debuggee.doTest();
    }
}