<commit_before>/*
 * Copyright 2016 OPS4J Contributors
 *
 * 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.ops4j.ramler.generator;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.lang.annotation.Annotation;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.annotation.Generated;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.ops4j.ramler.exc.Exceptions;
import org.raml.v2.api.model.v10.api.Api;
import org.raml.v2.api.model.v10.datamodel.FileTypeDeclaration;
import org.raml.v2.api.model.v10.datamodel.ObjectTypeDeclaration;
import org.raml.v2.api.model.v10.datamodel.TypeDeclaration;
import org.raml.v2.api.model.v10.methods.Method;
import org.raml.v2.api.model.v10.resources.Resource;

import com.sun.codemodel.JAnnotatable;
import com.sun.codemodel.JAnnotationArrayMember;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

public class ResourceGeneratingApiVisitor implements ApiVisitor {

    private GeneratorContext context;

    private JCodeModel codeModel;

    private JPackage pkg;

    private Map<String, Class<? extends Annotation>> httpMethodAnnotations;

    private JDefinedClass klass;

    private Resource outerResource;

    private Resource innerResource;
    
    private List<String> mediaTypes = Collections.emptyList();

    public ResourceGeneratingApiVisitor(GeneratorContext context) {
        this.context = context;
        this.codeModel = context.getCodeModel();
        this.pkg = context.getApiPackage();
        httpMethodAnnotations = Constants.JAXRS_HTTP_METHODS.stream()
            .collect(toMap(Class::getSimpleName, Function.identity()));
    }
    
    @Override
    public void visitApiStart(Api api) {
        mediaTypes = api.mediaType().stream().map(m -> m.value()).collect(toList());
    }

    @Override
    public void visitResourceStart(Resource resource) {
        try {
            if (outerResource == null) {
                outerResource = resource;
                klass = pkg
                    ._interface(Names.buildResourceInterfaceName(resource, context.getConfig()));
                klass.annotate(Generated.class).
                    param("value", "org.ops4j.ramler").
                    param("date", LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).toString());
                klass.annotate(Path.class).param("value", resource.resourcePath());
                
                if (mediaTypes.size() > 1) {
                    JAnnotationArrayMember producesArray = klass.annotate(Produces.class).paramArray("value");
                    mediaTypes.forEach(m -> producesArray.param(m));
                    JAnnotationArrayMember consumesArray = klass.annotate(Consumes.class).paramArray("value");
                    mediaTypes.forEach(m -> consumesArray.param(m));
                }
                else if (!mediaTypes.isEmpty()) {
                    klass.annotate(Produces.class).param("value", mediaTypes.get(0));
                    klass.annotate(Consumes.class).param("value", mediaTypes.get(0));
                }                
            }
            else if (innerResource == null) {
                innerResource = resource;
            }
            else {
                throw new IllegalStateException("cannot handle resources nested more than two levels");
            }

        }
        catch (JClassAlreadyExistsException exc) {
            throw Exceptions.unchecked(exc);
        }
    }

    @Override
    public void visitResourceEnd(Resource resource) {
        if (outerResource == resource) {
            klass = null;
            outerResource = null;
        }
        if (innerResource == resource) {
            innerResource = null;
        }
    }

    @Override
    public void visitMethodStart(Method method) {
        String methodName = Names.buildVariableName(method.displayName().value());
        JMethod codeMethod = klass.method(JMod.NONE, klass, methodName);
        addHttpMethodAnnotation(method.method(), codeMethod);

        if (innerResource != null) {
            codeMethod.annotate(Path.class).param("value", innerResource.relativeUri().value());
        }

        if (!method.body().isEmpty()) {
            TypeDeclaration body = method.body().get(0);
            if (body.name().equals("multipart/form-data")) {
                addFormParameters(codeMethod, body);
            }
            else {
                codeMethod.param(context.getJavaType(body), Names.buildVariableName(body.type()));
            }
        }

        for (TypeDeclaration pathParam : method.resource().uriParameters()) {
            JVar param = codeMethod.param(context.getJavaType(pathParam),
                Names.buildVariableName(pathParam.name()));
            param.annotate(PathParam.class).param("value", pathParam.name());
        }

        for (TypeDeclaration queryParam : method.queryParameters()) {
            JVar param = codeMethod.param(context.getJavaType(queryParam),
                Names.buildVariableName(queryParam.name()));
            param.annotate(QueryParam.class).param("value", queryParam.name());
            if (queryParam.defaultValue() != null) {
                param.annotate(DefaultValue.class).param("value", queryParam.defaultValue());
            }
        }

        if (method.responses().isEmpty()) {
            codeMethod.type(codeModel.VOID);
        }
        else {
            org.raml.v2.api.model.v10.bodies.Response response = method.responses().get(0);
            if (response.body().isEmpty()) {
                codeMethod.type(codeModel.VOID);
            }
            else {
                TypeDeclaration body = response.body().get(0);

                JType resultType = context.getJavaType(body);
                List<String> args = context.getApiModel().getStringAnnotations(body, "typeArgs");
                if (!args.isEmpty()) {
                    JClass jclass = (JClass) resultType;
                    for (String arg : args) {
                        JType typeArg = context.getModelPackage()._getClass(arg);
                        jclass = jclass.narrow(typeArg);
                    }
                    resultType = jclass;
                }
                
                codeMethod.type(resultType);
            }
        }
        if (method.displayName() != null) {
            codeMethod.javadoc().add(method.displayName().value());
        }
    }

    private void addFormParameters(JMethod codeMethod, TypeDeclaration body) {
        ObjectTypeDeclaration type = (ObjectTypeDeclaration) body;
        for (TypeDeclaration param : type.properties()) {
            addFormParameter(codeMethod, param);
        }
    }

    private void addFormParameter(JMethod codeMethod, TypeDeclaration formParam) {
        JVar param = codeMethod.param(context.getJavaType(formParam),
            Names.buildVariableName(formParam.name()));
        param.annotate(FormDataParam.class).param("value", formParam.name());

        if (formParam instanceof FileTypeDeclaration) {<commit_msg>refactored resource generator<commit_after>/*
 * Copyright 2016 OPS4J Contributors
 *
 * 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.ops4j.ramler.generator;

import static java.util.stream.Collectors.toList;
import static java.util.stream.Collectors.toMap;

import java.lang.annotation.Annotation;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.function.Function;

import javax.annotation.Generated;
import javax.ws.rs.Consumes;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import org.glassfish.jersey.media.multipart.FormDataContentDisposition;
import org.glassfish.jersey.media.multipart.FormDataParam;
import org.ops4j.ramler.exc.Exceptions;
import org.raml.v2.api.model.v10.api.Api;
import org.raml.v2.api.model.v10.datamodel.FileTypeDeclaration;
import org.raml.v2.api.model.v10.datamodel.ObjectTypeDeclaration;
import org.raml.v2.api.model.v10.datamodel.TypeDeclaration;
import org.raml.v2.api.model.v10.methods.Method;
import org.raml.v2.api.model.v10.resources.Resource;

import com.sun.codemodel.JAnnotatable;
import com.sun.codemodel.JClass;
import com.sun.codemodel.JClassAlreadyExistsException;
import com.sun.codemodel.JCodeModel;
import com.sun.codemodel.JDefinedClass;
import com.sun.codemodel.JMethod;
import com.sun.codemodel.JMod;
import com.sun.codemodel.JPackage;
import com.sun.codemodel.JType;
import com.sun.codemodel.JVar;

public class ResourceGeneratingApiVisitor implements ApiVisitor {

    private GeneratorContext context;

    private JCodeModel codeModel;

    private JPackage pkg;

    private Map<String, Class<? extends Annotation>> httpMethodAnnotations;

    private JDefinedClass klass;

    private Resource outerResource;

    private Resource innerResource;
    
    private List<String> mediaTypes = Collections.emptyList();

    public ResourceGeneratingApiVisitor(GeneratorContext context) {
        this.context = context;
        this.codeModel = context.getCodeModel();
        this.pkg = context.getApiPackage();
        httpMethodAnnotations = Constants.JAXRS_HTTP_METHODS.stream()
            .collect(toMap(Class::getSimpleName, Function.identity()));
    }
    
    @Override
    public void visitApiStart(Api api) {
        mediaTypes = api.mediaType().stream().map(m -> m.value()).collect(toList());
    }

    @Override
    public void visitResourceStart(Resource resource) {
        try {
            if (outerResource == null) {
                outerResource = resource;

                createResourceInterface(resource);                
                addMediaTypes();
            }
            else if (innerResource == null) {
                innerResource = resource;
            }
            else {
                throw new IllegalStateException("cannot handle resources nested more than two levels");
            }
        }
        catch (JClassAlreadyExistsException exc) {
            throw Exceptions.unchecked(exc);
        }
    }

    private void createResourceInterface(Resource resource) throws JClassAlreadyExistsException {
        klass = pkg
            ._interface(Names.buildResourceInterfaceName(resource, context.getConfig()));
        klass.annotate(Generated.class).
            param("value", "org.ops4j.ramler").
            param("date", LocalDateTime.now().truncatedTo(ChronoUnit.SECONDS).toString());
        klass.annotate(Path.class).param("value", resource.resourcePath());        
    }

    private void addMediaTypes() {
        if (mediaTypes.size() > 1) {
            mediaTypes.forEach(m -> klass.annotate(Produces.class).paramArray("value").param(m));
            mediaTypes.forEach(m -> klass.annotate(Consumes.class).paramArray("value").param(m));
        }
        else if (!mediaTypes.isEmpty()) {
            klass.annotate(Produces.class).param("value", mediaTypes.get(0));
            klass.annotate(Consumes.class).param("value", mediaTypes.get(0));
        }
    }

    @Override
    public void visitResourceEnd(Resource resource) {
        if (outerResource == resource) {
            klass = null;
            outerResource = null;
        }
        if (innerResource == resource) {
            innerResource = null;
        }
    }

    @Override
    public void visitMethodStart(Method method) {
        String methodName = Names.buildVariableName(method.displayName().value());
        JMethod codeMethod = klass.method(JMod.NONE, klass, methodName);
        
        addJavadoc(method, codeMethod);
        addSubresourcePath(codeMethod);
        addHttpMethodAnnotation(method.method(), codeMethod);
        addBodyParameters(method, codeMethod);
        addPathParameters(method, codeMethod);
        addQueryParameters(method, codeMethod);
        addReturnType(method, codeMethod);        
    }

    private void addSubresourcePath(JMethod codeMethod) {
        if (innerResource != null) {
            codeMethod.annotate(Path.class).param("value", innerResource.relativeUri().value());
        }
    }

    private void addJavadoc(Method method, JMethod codeMethod) {
        if (method.displayName() != null) {
            codeMethod.javadoc().add(method.displayName().value());
        }
    }

    private void addReturnType(Method method, JMethod codeMethod) {
        if (method.responses().isEmpty()) {
            codeMethod.type(codeModel.VOID);
        }
        else {
            org.raml.v2.api.model.v10.bodies.Response response = method.responses().get(0);
            if (response.body().isEmpty()) {
                codeMethod.type(codeModel.VOID);
            }
            else {
                TypeDeclaration body = response.body().get(0);
                JType resultType = context.getJavaType(body);
                resultType = addTypeArguments(resultType, body);                
                codeMethod.type(resultType);
            }
        }
    }

    private JType addTypeArguments(JType resultType, TypeDeclaration body) {
        List<String> args = context.getApiModel().getStringAnnotations(body, "typeArgs");
        if (!args.isEmpty()) {
            JClass jclass = (JClass) resultType;
            for (String arg : args) {
                JType typeArg = context.getModelPackage()._getClass(arg);
                jclass = jclass.narrow(typeArg);
            }
            resultType = jclass;
        }
        return resultType;
    }

    private void addQueryParameters(Method method, JMethod codeMethod) {
        for (TypeDeclaration queryParam : method.queryParameters()) {
            JVar param = codeMethod.param(context.getJavaType(queryParam),
                Names.buildVariableName(queryParam.name()));
            param.annotate(QueryParam.class).param("value", queryParam.name());
            if (queryParam.defaultValue() != null) {
                param.annotate(DefaultValue.class).param("value", queryParam.defaultValue());
            }
        }
    }

    private void addPathParameters(Method method, JMethod codeMethod) {
        for (TypeDeclaration pathParam : method.resource().uriParameters()) {
            JVar param = codeMethod.param(context.getJavaType(pathParam),
                Names.buildVariableName(pathParam.name()));
            param.annotate(PathParam.class).param("value", pathParam.name());
        }
    }

    private void addBodyParameters(Method method, JMethod codeMethod) {
        if (!method.body().isEmpty()) {
            TypeDeclaration body = method.body().get(0);
            if (body.name().equals("multipart/form-data")) {
                addFormParameters(codeMethod, body);
            }
            else {
                codeMethod.param(context.getJavaType(body), Names.buildVariableName(body.type()));
            }
        }
    }

    private void addFormParameters(JMethod codeMethod, TypeDeclaration body) {
        ObjectTypeDeclaration type = (ObjectTypeDeclaration) body;
        for (TypeDeclaration param : type.properties()) {
            addFormParameter(codeMethod, param);
        }
    }

    private void addFormParameter(JMethod codeMethod, TypeDeclaration formParam) {
        JVar param = codeMethod.param(context.getJavaType(formParam),
            Names.buildVariableName(formParam.name()));
        param.annotate(FormDataParam.class).param("value", formParam.name());

        if (formParam instanceof FileTypeDeclaration) {
{'fraction_non_alphanumeric': 0.10831598427018274, 'fraction_numerical': 0.004510756419153366, 'mean_word_length': 2.6072173550271174, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1923101', 'n_tokens_mistral': 4758, 'n_tokens_neox': 4610, 'n_words': 913}