/*
 * Copyright 2002-2008 the original author or authors.
 *
 * Licensed 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.springframework.config.java.internal.model;

import static org.springframework.config.java.internal.util.AnnotationExtractionUtils.extractMethodAnnotation;
import static org.springframework.config.java.internal.util.AnnotationExtractionUtils.findAnnotation;

import static java.lang.String.format;

import org.springframework.config.java.annotation.AutoBean;
import org.springframework.config.java.internal.util.MethodAnnotationPrototype;
import org.springframework.config.java.model.ModelClass;

import java.lang.annotation.Annotation;

import java.util.List;


/** TODO: JAVADOC */
public class AutoBeanMethod extends AbstractValidatableAnnotatedMethod<AutoBean> {

    private static final AutoBean defaultAnno = extractMethodAnnotation(AutoBean.class, new MethodAnnotationPrototype() {
            @AutoBean
            public void targetMethod() { }
        }.getClass());

    private final ModelClass returnType;

    public AutoBeanMethod(String name, ModelClass returnType, int modifiers, Annotation... annotations) {
        super(name, modifiers, annotations);
        this.returnType = returnType;
    }

    /**
     * for testing convenience.
     */
    AutoBeanMethod(String name, ModelClass returnType, int modifiers) {
        this(name, returnType, modifiers, defaultAnno);
    }

    public static boolean identifyAsExternalBeanMethod(Annotation[] annotations) {
        return (findAnnotation(AutoBean.class, annotations) != null);
    }

    public ModelClass getReturnType() { return returnType; }

    @Override
    public void detectUsageErrors(List<UsageError> errors) {
        super.detectUsageErrors(errors);

        if (returnType.isInterface())
            errors.add(new AbstractAutoBeanReturnTypeError());
    }

    @Override
    public String toString() {
        return format("%s; returnType=%s", super.toString(), returnType.getSimpleName());
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = super.hashCode();
        result = (prime * result) + ((returnType == null) ? 0 : returnType.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;

        if (!super.equals(obj))
            return false;

        if (getClass() != obj.getClass())
            return false;

        AutoBeanMethod other = (AutoBeanMethod) obj;
        if (returnType == null) {
            if (other.returnType != null)
                return false;
        } else if (!returnType.equals(other.returnType))
            return false;

        return true;
    }


    /** AutoBean methods must return a concrete type (non-abstract, non-interface). */
    public class AbstractAutoBeanReturnTypeError extends UsageError {
        public AbstractAutoBeanReturnTypeError() { super(getDeclaringClass(), getLineNumber()); }

        @Override
        public String getDescription() { return "@AutoBean methods must return a concrete type"; }
    }

}
