/*
 *  Licensed to the Apache Software Foundation (ASF) under one
 *  or more contributor license agreements.  See the NOTICE file
 *  distributed with this work for additional information
 *  regarding copyright ownership.  The ASF licenses this file
 *  to you under the Apache License, Version 2.0 (the
 *  "License"); you may not use this file except in compliance
 *  with the License.  You may obtain a copy of the License at
 *
 *    http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an
 *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *  KIND, either express or implied.  See the License for the
 *  specific language governing permissions and limitations
 *  under the License.
 */
package org.apache.groovy.ast.tools;

import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.MethodNode;
import org.codehaus.groovy.ast.Parameter;
import org.codehaus.groovy.ast.stmt.BlockStatement;
import org.codehaus.groovy.ast.stmt.Statement;

import static org.apache.groovy.util.BeanUtils.decapitalize;

/**
 * Utility class for working with MethodNodes
 */
public class MethodNodeUtils {
    /**
     * Return the method node's descriptor including its
     * name and parameter types without generics.
     *
     * @param mNode the method node
     * @return the method node's abbreviated descriptor excluding the return type
     */
    public static String methodDescriptorWithoutReturnType(MethodNode mNode) {
        StringBuilder sb = new StringBuilder();
        sb.append(mNode.getName()).append(':');
        for (Parameter p : mNode.getParameters()) {
            sb.append(ClassNodeUtils.formatTypeName(p.getType())).append(',');
        }
        return sb.toString();
    }

    /**
     * Return the method node's descriptor which includes its return type,
     * name and parameter types without generics.
     *
     * @param mNode the method node
     * @return the method node's descriptor
     */
    public static String methodDescriptor(MethodNode mNode) {
        StringBuilder sb = new StringBuilder(mNode.getName().length() + mNode.getParameters().length * 10);
        sb.append(mNode.getReturnType().getName());
        sb.append(' ');
        sb.append(mNode.getName());
        sb.append('(');
        for (int i = 0; i < mNode.getParameters().length; i++) {
            if (i > 0) {
                sb.append(", ");
            }
            Parameter p = mNode.getParameters()[i];
            sb.append(ClassNodeUtils.formatTypeName(p.getType()));
        }
        sb.append(')');
        return sb.toString();
    }

    /**
     * For a method node potentially representing a property, returns the name of the property.
     *
     * @param mNode a MethodNode
     * @return the property name without the get/set/is prefix if a property or null
     */
    public static String getPropertyName(MethodNode mNode) {
        String name = mNode.getName();
        final boolean startsWithSet = name.startsWith("set");
        final boolean startsWithGet = name.startsWith("get");
        final boolean startsWithIs = name.startsWith("is");
        if (startsWithGet || startsWithSet || startsWithIs) {
            final String tmpPname = name.substring(startsWithIs ? 2 : 3);
            if (!tmpPname.isEmpty()) {
                if (startsWithSet) {
                    if (mNode.getParameters().length == 1) {
                        return decapitalize(tmpPname);
                    }
                } else if (mNode.getParameters().length == 0 && !ClassHelper.VOID_TYPE.equals(mNode.getReturnType())) {
                    if (startsWithGet || ClassHelper.boolean_TYPE.equals(mNode.getReturnType())) {
                        return decapitalize(tmpPname);
                    }
                }
            }
        }
        return null;
    }

    private MethodNodeUtils() { }

    /**
     * Gets the code for a method (or constructor) as a block.
     * If no code is found, an empty block will be returned.
     * If a single non-block statement is found, a block containing that statement will be returned.
     * Otherwise the existing block statement will be returned.
     * The original {@code node} is not modified.
     *
     * @param node the method (or constructor) node
     * @return the found or created block statement
     */
    public static BlockStatement getCodeAsBlock(MethodNode node) {
        Statement code = node.getCode();
        BlockStatement block;
        if (code == null) {
            block = new BlockStatement();
        } else if (!(code instanceof BlockStatement)) {
            block = new BlockStatement();
            block.addStatement(code);
        } else {
            block = (BlockStatement) code;
        }
        return block;
    }
}

@@ -3001,37 +3001,37 @@
     
-String name = mNode.getName()
+boolean startsWithGet = false
 ;%0A  
@@ -3028,38 +3028,32 @@
  false;%0A        
-final 
 boolean startsWi
@@ -3064,54 +3064,106 @@
 t = 
-name.startsWith(%22set%22);%0A        final boolean 
+false;%0A        boolean startsWithIs = false;%0A        String name = mNode.getName();%0A%0A        if ((
 star
@@ -3196,17 +3196,17 @@
 h(%22get%22)
-;
+)
 %0A       
@@ -3210,22 +3210,20 @@
     
-final boolean 
+        %7C%7C (
 star
@@ -3228,18 +3228,19 @@
 artsWith
-Is
+Set
  = name.
@@ -3255,13 +3255,14 @@
 th(%22
-i
 s
+et
 %22)
-;
+)
 %0A   
@@ -3266,37 +3266,28 @@
         
-if (startsWithGet
+       
  %7C%7C 
+(
 startsWi
@@ -3292,15 +3292,18 @@
 With
-Set %7C%7C 
+Is = name.
 star
@@ -3312,14 +3312,20 @@
 With
-Is
+(%22is%22))
 ) %7B%0A
+%0A
     
@@ -3938,32 +3938,33 @@
     %7D%0A        %7D%0A
+%0A
         return n

Improve the performance of `getPropertyName` further