/*
* JBoss, Home of Professional Open Source
* Copyright 2009-10 Red Hat and individual contributors
* by the @authors tag. See the copyright.txt in the distribution for a
* full listing of individual contributors.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*
* @authors Andrew Dinn
*/
package org.jboss.byteman.agent;

import org.objectweb.asm.ClassVisitor;
import org.jboss.byteman.rule.type.TypeHelper;
import org.jboss.byteman.agent.adapter.*;

/**
 * Specifies a location in a method at which a rule trigger should be inserted
 */
public abstract class Location
{
    /**
     * create a location object of a given type
     * @param type the type of location being specified
     * @param parameters the text of the parameters appended to the location specifier
     * @return a location of the appropriate type or null if the parameters are incorrectly specified
     */
    public static Location create(LocationType type, String parameters)
    {
        switch (type)
        {
            case ENTRY:
                return EntryLocation.create(parameters);
            case LINE:
                return LineLocation.create(parameters);
            case READ:
                return AccessLocation.create(parameters, ACCESS_READ, false);
            case READ_COMPLETED:
                return AccessLocation.create(parameters, ACCESS_READ, true);
            case WRITE:
                return AccessLocation.create(parameters, ACCESS_WRITE, false);
            case WRITE_COMPLETED:
                return AccessLocation.create(parameters, ACCESS_WRITE, true);
            case INVOKE:
                return InvokeLocation.create(parameters, false);
            case INVOKE_COMPLETED:
                return InvokeLocation.create(parameters, true);
            case SYNCHRONIZE:
                return SynchronizeLocation.create(parameters, false);
            case SYNCHRONIZE_COMPLETED:
                return SynchronizeLocation.create(parameters, true);
            case THROW:
                return ThrowLocation.create(parameters);
            case EXIT:
                return ExitLocation.create(parameters);
            case EXCEPTION_EXIT:
                return ExceptionExitLocation.create(parameters);
            case NEW:
                return NewLocation.create(parameters, false);
            case NEW_COMPLETED:
                return NewLocation.create(parameters, true);
        }

        return null;
    }

    /**
     * return an adapter which can be used to check whether a method contains a trigger point whose position
     * matches this location
     * @param cv the current class visitor
     * @param transformContext the current transform context
     * @return the required adapter
     */
    public abstract RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext);

    /**
     * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
     * position matches this location
     * @param cv the current class visitor
     * @param transformContext the current transform context
     * @return the required adapter
     */
    public abstract RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext);

    /**
     * identify the type of this location
     * @return the type of this location
     */
    public abstract LocationType getLocationType();

    /**
     * flag indicating that a field access location refers to field READ operations
     */
    public static final int ACCESS_READ = 1;

    /**
     * flag indicating that a field access location refers to field WRITE operations
     */
    public static final int ACCESS_WRITE = 2;

    /**
     * location identifying a method entry trigger point
     */
    private static class EntryLocation extends Location
    {
        /**
         * create a location identifying a method entry trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters) {
            if (!parameters.trim().equals("")) {
                // hmm, not expecting any parameters here
                return null;
            }
            return new EntryLocation();
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @param cv the current class visitor
         * @param transformContext the current transform context
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new EntryCheckAdapter(cv, transformContext);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @param cv the current class visitor
         * @param transformContext the current transform context
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new EntryTriggerAdapter(cv, transformContext);
        }

        public LocationType getLocationType() {
            return LocationType.ENTRY;
        }

        public String toString() {
            return "AT ENTRY";
        }
    }

    /**
     * location identifying a method line trigger point
     */
    private static class LineLocation extends Location
    {
        /**
         * the line at which the trigger point should be inserted
         */
        private int targetLine;

        /**
         * construct a location identifying a method line trigger point
         * @param targetLine the line at which the trigger point should be inserted
         */
        private LineLocation(int targetLine)
        {
            this.targetLine = targetLine;
        }

        /**
         * create a location identifying a method entry trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters)
        {
            try {
                int targetLine = Integer.decode(parameters.trim());
                return new LineLocation(targetLine);
            } catch (NumberFormatException nfe) {
                return null;
            }
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @param cv the current class visitor
         * @param transformContext the current transform context
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new LineCheckAdapter(cv, transformContext, targetLine);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @param cv the current class visitor
         * @param transformContext the current transform context
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new LineTriggerAdapter(cv, transformContext, targetLine);
        }

        public LocationType getLocationType() {
            return LocationType.LINE;
        }

        public String toString() {
            return "AT LINE " + targetLine;
        }
    }

    /**
     * location identifying a generic access trigger point
     */
    private static abstract class AccessLocation extends Location
    {
        /**
         * count identifying which access should be taken as the trigger point. if not specified
         * as a parameter this defaults to the first access.
         */
        protected int count;

        /**
         * flags identifying which type of access should be used to identify the trigger. this is either
         * ACCESS_READ, ACCESS_WRITE or an OR of these two values
         */
        protected int flags;

        /**
         * flag which is false if the trigger should be inserted before the field access is performed
         * and true if it should be inserted after
         */
        protected boolean whenComplete;

        protected AccessLocation(int count, int flags, boolean whenComplete) {
            this.count = count;
            this.flags = flags;
            this.whenComplete = whenComplete;
        }

        /**
         * create a location identifying a method entry trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying
         * whether this specifies field READ or WRITE operations
         * @param whenComplete false if the trigger should be inserted before the access is performed
         * and true if it should be inserted after
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters, int flags, boolean whenComplete)
        {
            String text = parameters.trim();
            int count;

            // check for trailing count
            if (text.contains(" ")) {
                int spaceIdx = text.lastIndexOf(" ");
                String countText = text.substring(spaceIdx).trim();
                if (countText.equals("ALL")) {
                    // a zero count means match all
                    count = 0;
                } else {
                    try {
                        count = Integer.valueOf(countText);
                    } catch (NumberFormatException nfe) {
                        return null;
                    }
                }
                text = text.substring(0, spaceIdx).trim();
            } else {
                count = 1;
            }
            if (text.equals("")) {
                return null;
            }

            // check for a local or parameter var name identified by a leading $
            if (text.startsWith("$")) {
                String varname = text.substring(1).trim();
                return new VariableAccessLocation(varname, count, flags, whenComplete);
            } else {
                String typeName;
                String fieldName;
                // check for leading type name
                if (text.contains(".")) {
                    int dotIdx = text.lastIndexOf(".");
                    typeName = text.substring(0, dotIdx).trim();
                    fieldName=text.substring(dotIdx + 1).trim();
                } else {
                    typeName = null;
                    fieldName = text;
                }
                // TODO sanity check type and field name

                return new FieldAccessLocation(typeName, fieldName, count, flags, whenComplete);
            }
        }

        public LocationType getLocationType() {
            if ((flags & ACCESS_WRITE) != 0) {
                if (whenComplete) {
                    return LocationType.WRITE_COMPLETED;
                } else {
                    return LocationType.WRITE;
                }
            } else {
                if (whenComplete) {
                    return LocationType.READ_COMPLETED;
                } else {
                    return LocationType.READ;
                }
            }
        }
    }

    /**
     * location identifying a field access trigger point
     */
    private static class FieldAccessLocation extends AccessLocation
    {
        /**
         * the name of the field being accessed at the point where the trigger point should be inserted
         */
        private String fieldName;

        /**
         * the name of the type to which the field belongs or null if any type will do
         */
        private String typeName;

        /**
         * construct a location identifying a field read trigger point
         * @param typeName the name of the class owning the field
         * @param fieldName the name of the field being read
         * @param count count identifying which access should be taken as the trigger point
         * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying
         * whether this specifies field READ or WRITE operations
         * @param whenComplete false if the trigger should be inserted before the access is performed
         * and true if it should be inserted after
         */
        private FieldAccessLocation(String typeName, String fieldName, int count, int flags, boolean whenComplete)
        {
            super(count, flags, whenComplete);
            this.typeName = typeName;
            this.fieldName = fieldName;
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new FieldAccessCheckAdapter(cv, transformContext, typeName, fieldName, flags, count);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new FieldAccessTriggerAdapter(cv, transformContext, typeName, fieldName, flags, count, whenComplete);
        }

        public String toString() {
            String text;

            if (whenComplete) {
                text = "AFTER ";
            } else {
                text = "AT ";
            }
            if (flags == ACCESS_READ) {
                text += "READ ";
            } else if (flags == ACCESS_WRITE) {
                text += "WRITE ";
            } else {
                text += "ACCESS ";
            }

            if (typeName != null) {
                text += typeName + ".";
            }
            text += fieldName;

            if (count != 1) {
                if (count == 0) {
                    text += " ALL";
                } else {
                    text += " " + count;
                }
            }

            return text;
        }
    }

    /**
     * location identifying a variable access trigger point
     */
    private static class VariableAccessLocation extends AccessLocation
    {
        /**
         * the name of the variable being accessed at the point where the trigger point should be inserted
         */
        private String variableName;

        /**
         * flag which is true if the name is a method parameter index such as $0, $1 etc otherwise false
         */
        private boolean isIndex;

        /**
         * construct a location identifying a variable read trigger point
         * @param variablename the name of the variable being read
         * @param count count identifying which access should be taken as the trigger point
         * @param flags bit field comprising one or other of flags ACCESS_READ and ACCESS_WRITE identifying
         * whether this specifies field READ or WRITE operations
         * @param whenComplete false if the trigger should be inserted before the access is performed
         * and true if it should be inserted after
         */
        protected VariableAccessLocation(String variablename, int count, int flags, boolean whenComplete)
        {
            super(count, flags, whenComplete);
            this.variableName = variablename;
            isIndex = variablename.matches("[0-9]+");
        }


        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            if (isIndex) {
                int paramIdx = Integer.valueOf(variableName);
                return new IndexParamAccessCheckAdapter(cv, transformContext, paramIdx, flags, count);
            } else {
                // we need to insert a BMJSRInliner into the pipeline so that the check adapter gets
                // notified when local vars go in and out of scope
                return new VariableAccessCheckAdapter(cv, transformContext, variableName, flags, count);
            }
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            if (isIndex) {
                int paramIdx = Integer.valueOf(variableName);
                return new IndexParamAccessTriggerAdapter(cv, transformContext, paramIdx, flags, count, whenComplete);
            } else {
                return new VariableAccessTriggerAdapter(cv, transformContext, variableName, flags, count, whenComplete);
            }
        }

        public LocationType getLocationType() {
            if ((flags & ACCESS_WRITE) != 0) {
                if (whenComplete) {
                    return LocationType.WRITE_COMPLETED;
                } else {
                    return LocationType.WRITE;
                }
            } else {
                if (whenComplete) {
                    return LocationType.READ_COMPLETED;
                } else {
                    return LocationType.READ;
                }
            }
        }

        public String toString() {
            String text;

            if (whenComplete) {
                text = "AFTER ";
            } else {
                text = "AT ";
            }
            if (flags == ACCESS_READ) {
                text += "READ ";
            } else if (flags == ACCESS_WRITE) {
                text += "WRITE ";
            } else {
                text += "ACCESS ";
            }

            text += "$";
            
            text += variableName;

            if (count != 1) {
                if (count == 0) {
                    text += " ALL";
                } else {
                    text += " " + count;
                }
            }

            return text;
        }
    }

    /**
     * location identifying a method invocation trigger point
     */
    private static class InvokeLocation extends Location
    {
        /**
         * the name of the method being invoked at the point where the trigger point should be inserted
         */
        private String methodName;

        /**
         * the name of the type to which the method belongs or null if any type will do
         */
        private String typeName;

        /**
         * the method signature in externalised form
         */
        private String signature;

        /**
         * count identifying which invocation should be taken as the trigger point. if not specified
         * as a parameter this defaults to the first invocation.
         */
        private int count;

        /**
         * flag which is false if the trigger should be inserted before the method invocation is performed
         * and true if it should be inserted after
         */
        private boolean whenComplete;

        /**
         * construct a location identifying a method invocation trigger point
         * @param typeName the name of the class owning the method
         * @param methodName the name of the method being called
         * @param signature the method signature in externalised form
         * @param count count identifying which invocation should be taken as the trigger point
         * @param whenComplete false if the trigger should be inserted before the method invocation is
         * performed and true if it should be inserted after
         */
        private InvokeLocation(String typeName, String methodName, String signature, int count, boolean whenComplete)
        {
            this.typeName = typeName;
            this.methodName = methodName;
            this.signature = signature;
            this.count = count;
            this.whenComplete = whenComplete;
        }

        /**
         * create a location identifying a method entry trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @param whenComplete false if the trigger should be inserted before the access is performed
         * and true if ti shoudl be inserted after
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters, boolean whenComplete)
        {
            String text = parameters.trim();
            String typeName;
            String fieldName;
            String signature;
            int count;

            // check for trailing count
            if (text.contains(")")) {
                int tailIdx = text.lastIndexOf(")");
                String countText = text.substring(tailIdx + 1).trim();
                if (!countText.equals("")) {
                    if (countText.equals("ALL")) {
                        // a zero count means all
                        count = 0;
                    } else {
                        try {
                            count = Integer.valueOf(countText);
                        } catch (NumberFormatException nfe) {
                            return null;
                        }
                    }
                } else {
                    count = 1;
                }
                text = text.substring(0, tailIdx + 1).trim();
            } else if (text.contains(" ")) {
                int tailIdx = text.lastIndexOf(" ");
                String countText = text.substring(tailIdx + 1).trim();
                if (!countText.equals("")) {
                    if (countText.equals("ALL")) {
                        // a zero count means all
                        count = 0;
                    } else {
                        try {
                            count = Integer.valueOf(countText);
                        } catch (NumberFormatException nfe) {
                            return null;
                        }
                    }
                } else {
                    count = 1;
                }
                text = text.substring(0, tailIdx).trim();
            } else {
                count = 1;
            }
            // check for argument list
            if (text.contains("(")) {
                signature = TypeHelper.parseMethodDescriptor(text);
                text=TypeHelper.parseMethodName(text);
            } else {
                signature = "";
            }
            // check for leading type name
            if (text.contains(".")) {
                int dotIdx = text.lastIndexOf(".");
                typeName = text.substring(0, dotIdx).trim();
                fieldName=text.substring(dotIdx + 1).trim();
            } else {
                typeName = null;
                fieldName = text;
            }
            // TODO sanity check type and field name

            return new InvokeLocation(typeName, fieldName, signature, count, whenComplete);
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new InvokeCheckAdapter(cv, transformContext, typeName, methodName, signature, count);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new InvokeTriggerAdapter(cv, transformContext, typeName, methodName, signature, count, whenComplete);
        }

        public LocationType getLocationType() {
            if (whenComplete) {
                return LocationType.INVOKE_COMPLETED;
            } else {
                return LocationType.INVOKE;
            }
        }

        public String toString() {
            String text;

            if (whenComplete) {
                text = "AFTER INVOKE ";
            } else {
                text = "AT INVOKE ";
            }

            if (typeName != null) {
                text += typeName + ".";
            }
            text += methodName;

            if (signature.length() > 0) {
                text = text + TypeHelper.internalizeDescriptor(signature);
            }
            
            if (count != 1) {
                if (count == 0) {
                    text += " ALL";
                } else {
                    text += " " + count;
                }
            }

            return text;
        }
    }

    /**
     * location identifying a synchronization trigger point
     */
    private static class SynchronizeLocation extends Location
    {
        /**
         * count identifying which synchronization should be taken as the trigger point. if not specified
         * as a parameter this defaults to the first synchronization.
         */
        private int count;

        /**
         * flag which is false if the trigger should be inserted before the synchronization is performed
         * and true if it should be inserted after
         */
        private boolean whenComplete;

        /**
         * construct a location identifying a synchronization trigger point
         * @param count count identifying which synchronization should be taken as the trigger point
         * @param whenComplete false if the trigger should be inserted before the synchronization is
         * performed and true if it should be inserted after
         */
        private SynchronizeLocation(int count, boolean whenComplete)
        {
            this.count = count;
            this.whenComplete = whenComplete;
        }

        /**
         * create a location identifying a synchronization trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @param whenComplete false if the trigger should be inserted before the synchronization is
         * performed and true if it should be inserted after
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters, boolean whenComplete)
        {
            String text = parameters.trim();
            int count;

            // check for count
            if (text.length() != 0) {
                if (text.equals("ALL")) {
                    // a zero count means all
                    count = 0;
                } else {
                    try {
                        count = Integer.valueOf(text);
                    } catch (NumberFormatException nfe) {
                        return null;
                    }
                }
            } else {
                count = 1;
            }

            return new SynchronizeLocation(count, whenComplete);
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new SynchronizeCheckAdapter(cv, transformContext, count);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new SynchronizeTriggerAdapter(cv, transformContext, count, whenComplete);
        }

        public LocationType getLocationType() {
            if (whenComplete) {
                return LocationType.SYNCHRONIZE_COMPLETED;
            } else {
                return LocationType.SYNCHRONIZE;
            }
        }

        public String toString() {
            String text;

            if (whenComplete) {
                text= "AFTER SYNCHRONIZE";
            } else {
                text= "AT SYNCHRONIZE";
            }

            if (count != 1) {
                if (count == 0) {
                    text += " ALL";
                } else {
                    text += " " + count;
                }
            }

            return text;
        }
    }
    /**
     * location identifying a throw trigger point
     */
    private static class ThrowLocation extends Location
    {
        /**
         * count identifying which throw operation should be taken as the trigger point. if not specified
         * as a parameter this defaults to the first throw.
         */
        private int count;

        /**
         * the name of the exception type to which the method belongs or null if any type will do
         */
        private String typeName;

        /**
         * construct a location identifying a throw trigger point
         * @param count count identifying which throw should be taken as the trigger point
         * @param typeName the name of the exception type associated with the throw operation
         */
        private ThrowLocation(int count, String typeName)
        {
            this.count = count;
            this.typeName = typeName;
        }

        /**
         * create a location identifying a throw trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a throw location or null if the parameters does not contain a valid type name
         */
        protected static Location create(String parameters)
        {
            String text = parameters.trim();
            String typeName = "";
            int count;

            // text may be either blank, a count or ALL
            if (text.equals("")) {
                // count defaults to 1
                count = 1;
            } else if (text.equals("ALL")) {
                // a zero count means all
                count = 0;
            } else {
                try {
                    count = Integer.valueOf(text);
                } catch (NumberFormatException nfe) {
                    return null;
                }
            }

            // TODO sanity check type name

            return new ThrowLocation(count, typeName);
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new ThrowCheckAdapter(cv, transformContext, typeName, count);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            return new ThrowTriggerAdapter(cv, transformContext, typeName, count);
        }

        public LocationType getLocationType() {
            return LocationType.THROW;
        }

        public String toString() {
            String text = "AT THROW";

            if (count != 1) {
                if (count == 0) {
                    text += " ALL";
                } else {
                    text += " " + count;
                }
            }

            return text;
        }
    }
    /**
     * location identifying a method exit trigger point
     */
    private static class ExitLocation extends Location
    {
        /**
         * create a location identifying a method entry trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters) {
            if (!parameters.trim().equals("")) {
                // hmm, not expecting any parameters here
                return null;
            }
            return new ExitLocation();
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            // a line check adapter with line -1 will do the job

            return new ExitCheckAdapter(cv, transformContext);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            // a line adapter with line -1 will do the job

            return new ExitTriggerAdapter(cv, transformContext);
        }

        public LocationType getLocationType() {
            return LocationType.EXIT;
        }

        public String toString() {
            return "AT EXIT";
        }
    }

    /**
     * location identifying a method exceptional exit trigger point
     */
    private static class ExceptionExitLocation extends Location
    {
        /**
         * create a location identifying a method exceptional exit trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters) {
            if (!parameters.trim().equals("")) {
                // hmm, not expecting any parameters here
                return null;
            }
            return new ExceptionExitLocation();
        }

        /**
         * return an adapter which can be used to check whether a method contains a trigger point whose position
         * matches this location
         * @return the required adapter
         */
        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext) {
            // a line check adapter with line -1 will do the job

            return new ExceptionExitCheckAdapter(cv, transformContext);
        }

        /**
         * return an adapter which can be used to insert a trigger call in a method containing a trigger point whose
         * position matches this location
         * @return the required adapter
         */
        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext) {
            // a line adapter with line -1 will do the job

            return new ExceptionExitTriggerAdapter(cv, transformContext);
        }

        public LocationType getLocationType() {
            return LocationType.EXCEPTION_EXIT;
        }

        public String toString() {
            return "AT EXCEPTION EXIT";
        }
    }

    private static class NewLocation extends Location
    {
        /**
         * the name of the new type being created or the empty String if
         * no typename was specified
         */
        private String typeName;

        /**
         * count identifying which new operation should be taken as the trigger point.
         * if not specified as a parameter this defaults to the first invocation. if
         * 'ALL' was specified this takes value 0.
         */
        private int count;

        /**
         * number of array dimensions that should be matched at an array allocation site
         * or 0 if plain, non-array object allocations should be matched
         */
        int dims;

        /**
         * flag which is false if the trigger should be inserted before the method invocation is performed
         * and true if it should be inserted after
         */
        private boolean whenComplete;

        private NewLocation(String typeName, int count, int dims, boolean whenComplete)
        {
            this.typeName = typeName;
            this.count = count;
            this.dims = dims;
            this.whenComplete = whenComplete;
        }

        /**
         * create a location identifying a method exceptional exit trigger point
         * @param parameters the text of the parameters appended to the location specifier
         * @return a method entry location or null if the parameters is not a blank String
         */
        protected static Location create(String parameters, boolean whenComplete) {
            // syntax is AT NEW [{typename}] [{count} | 'ALL']
            String text = parameters.trim();
            String typeNamePattern = "[A-Za-z][A-Za-z0-9_]+";
            String arrayDimsPattern = "(\\[\\])+";
            String countPattern = "[0-9]+";
            String allPattern = "ALL";
            String countText = null;
            String typeNameText = null;
            String typeName;
            int count;
            int dims;

            if (text.contains(" ")) {
                int tailIdx = text.lastIndexOf(" ");
                countText = text.substring(tailIdx + 1).trim();
                typeNameText = text.substring(0, tailIdx).trim();
            } else if (text.matches(allPattern) || text.matches(countPattern)) {
                typeNameText = null;
                countText = text;
            } else {
                typeNameText = text;
                countText = null;
            }

            if (countText == null) {
                count = 1;
            } else if (countText.matches(allPattern)) {
                count = 0;
            } else if (countText.matches(countPattern)){
                try {
                    count = Integer.valueOf(countText);
                } catch (NumberFormatException nfe) {
                    return null;
                }
            } else if (countText.matches(arrayDimsPattern)) {
                // space was separating array dims from type
                // glue it back together and continue.
                typeNameText = typeNameText + countText;
                count = 1;
            } else {
                return null;
            }

            if (typeNameText == null || typeNameText.length() == 0) {
                typeName = "";
                dims = 0;
            } else if (typeNameText.matches(typeNamePattern)) {
                typeName = typeNameText;
                dims = 0;
            } else if (typeNameText.matches(arrayDimsPattern)) {
                typeName = "";
                dims  = typeNameText.length() / 2;
            } else {
                if (!typeNameText.contains("[")) {
                    return null;
                }
                int tailIdx = typeNameText.indexOf("[");
                String dimsText = typeNameText.substring(tailIdx);
                typeNameText = typeNameText.substring(0, tailIdx);

                if (typeNameText.matches(typeNamePattern) && dimsText.matches(arrayDimsPattern)) {
                    typeName = typeNameText;
                    dims = dimsText.length() / 2;
                } else {
                    return null;
                }
            }

            return new NewLocation(typeName, count, dims, whenComplete);
        }

        public RuleCheckAdapter getRuleCheckAdapter(ClassVisitor cv, TransformContext transformContext)
        {
            if (dims == 0) {
                return new NewCheckAdapter(cv, transformContext, typeName, count, whenComplete);
            } else {
                return new NewArrayCheckAdapter(cv, transformContext, typeName, count, dims, whenComplete);
            }
        }

        public RuleTriggerAdapter getRuleAdapter(ClassVisitor cv, TransformContext transformContext)
        {
            if (dims == 0) {
                return new NewTriggerAdapter(cv, transformContext, typeName, count, whenComplete);
            } else {
                return new NewArrayTriggerAdapter(cv, transformContext, typeName, count, dims, whenComplete);
            }
        }

        public LocationType getLocationType()
        {
            return (whenComplete ? LocationType.NEW_COMPLETED : LocationType.NEW);
        }

        public String toString() {
            StringBuilder builder = new StringBuilder();
            builder.append((whenComplete ? "AFTER NEW" : "AT NEW"));
            if (typeName != null) {
                builder.append(" ");
                builder.append(typeName);
            }
            for (int i = 0; i < dims; i++) {
                builder.append("[]");
            }
            if (count == 0) {
                builder.append(" ALL");
            } else if (count != 1) {
                builder.append(" ");
                builder.append(count);
            }
            return builder.toString();
        }
    }
}
