/*
 * Copyright 1998-2008 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */

package com.sun.tools.example.debug.tty;

import com.sun.jdi.*;
import com.sun.jdi.request.EventRequest;
import com.sun.jdi.request.ExceptionRequest;
import com.sun.jdi.request.ClassPrepareRequest;
import com.sun.jdi.event.ClassPrepareEvent;
import java.util.List;
import java.util.ArrayList;
import java.util.Iterator;

abstract class EventRequestSpec {

    final ReferenceTypeSpec refSpec;

    int suspendPolicy = EventRequest.SUSPEND_ALL;

    EventRequest resolved = null;
    ClassPrepareRequest prepareRequest = null;

    EventRequestSpec(ReferenceTypeSpec refSpec) {
        this.refSpec = refSpec;
    }

    /**
     * The 'refType' is known to match, return the EventRequest.
     */
    abstract EventRequest resolveEventRequest(ReferenceType refType)
                                           throws Exception;

    /**
     * @return If this EventRequestSpec matches the 'refType'
     * return the cooresponding EventRequest.  Otherwise
     * return null.
     */
    synchronized EventRequest resolve(ClassPrepareEvent event) throws Exception {
        if ((resolved == null) &&
            (prepareRequest != null) &&
            prepareRequest.equals(event.request())) {

            resolved = resolveEventRequest(event.referenceType());
            prepareRequest.disable();
            Env.vm().eventRequestManager().deleteEventRequest(prepareRequest);
            prepareRequest = null;

            if (refSpec instanceof PatternReferenceTypeSpec) {
                PatternReferenceTypeSpec prs = (PatternReferenceTypeSpec)refSpec;
                if (! prs.isUnique()){
                    /*
                     * Class pattern event requests are never
                     * considered "resolved", since future class loads
                     * might also match.
                     * Create and enable a new ClassPrepareRequest to
                     * keep trying to resolve.
                     */
                    resolved = null;
                    prepareRequest = refSpec.createPrepareRequest();
                    prepareRequest.enable();
                }
            }
        }
        return resolved;
    }

    synchronized void remove() {
        if (isResolved()) {
            Env.vm().eventRequestManager().deleteEventRequest(resolved());
        }
        if (refSpec instanceof PatternReferenceTypeSpec) {
            PatternReferenceTypeSpec prs = (PatternReferenceTypeSpec)refSpec;
            if (! prs.isUnique()){
                /*
                 * This is a class pattern.  Track down and delete
                 * all EventRequests matching this spec.
                 * Note: Class patterns apply only to ExceptionRequests,
                 * so that is all we need to examine.
                 */
                ArrayList<ExceptionRequest> deleteList = new ArrayList<ExceptionRequest>();
                for (ExceptionRequest er :
                         Env.vm().eventRequestManager().exceptionRequests()) {
                    if (prs.matches(er.exception())) {
                        deleteList.add (er);
                    }
                }
                Env.vm().eventRequestManager().deleteEventRequests(deleteList);
            }
        }
    }

    private EventRequest resolveAgainstPreparedClasses() throws Exception {
        for (ReferenceType refType : Env.vm().allClasses()) {
            if (refType.isPrepared() && refSpec.matches(refType)) {
                resolved = resolveEventRequest(refType);
            }
        }
        return resolved;
    }

    synchronized EventRequest resolveEagerly() throws Exception {
        try {
            if (resolved == null) {
                /*
                 * Not resolved.  Schedule a prepare request so we
                 * can resolve later.
                 */
                prepareRequest = refSpec.createPrepareRequest();
                prepareRequest.enable();

                // Try to resolve in case the class is already loaded.
                resolveAgainstPreparedClasses();
                if (resolved != null) {
                    prepareRequest.disable();
                    Env.vm().eventRequestManager().deleteEventRequest(prepareRequest);
                    prepareRequest = null;
                }
            }
            if (refSpec instanceof PatternReferenceTypeSpec) {
                PatternReferenceTypeSpec prs = (PatternReferenceTypeSpec)refSpec;
                if (! prs.isUnique()){
                    /*
                     * Class pattern event requests are never
                     * considered "resolved", since future class loads
                     * might also match.  Create a new
                     * ClassPrepareRequest if necessary and keep
                     * trying to resolve.
                     */
                    resolved = null;
                    if (prepareRequest == null) {
                        prepareRequest = refSpec.createPrepareRequest();
                        prepareRequest.enable();
                    }
                }
            }
        } catch (VMNotConnectedException e) {
            // Do nothing. Another resolve will be attempted when the
            // VM is started.
        }
        return resolved;
    }

    /**
     * @return the eventRequest this spec has been resolved to,
     * null if so far unresolved.
     */
    EventRequest resolved() {
        return resolved;
    }

    /**
     * @return true if this spec has been resolved.
     */
    boolean isResolved() {
        return resolved != null;
    }

    protected boolean isJavaIdentifier(String s) {
        if (s.length() == 0) {
            return false;
        }

        int cp = s.codePointAt(0);
        if (! Character.isJavaIdentifierStart(cp)) {
            return false;
        }

        for (int i = Character.charCount(cp); i < s.length(); i += Character.charCount(cp)) {
            cp = s.codePointAt(i);
            if (! Character.isJavaIdentifierPart(cp)) {
                return false;
            }
        }

        return true;
    }

    String errorMessageFor(Exception e) {
        if (e instanceof IllegalArgumentException) {
            return (MessageOutput.format("Invalid command syntax"));
        } else if (e instanceof RuntimeException) {
            // A runtime exception that we were not expecting
            throw (RuntimeException)e;
        } else {
            return (MessageOutput.format("Internal error; unable to set",
                                         this.refSpec.toString()));
        }
    }
}
