/*
* Copyright (C) 2015 Sebastian Daschner, sebastian-daschner.com
*
* 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/LICENSE2.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 com.sebastian_daschner.jaxrs_analyzer.analysis.results;
import com.sebastian_daschner.jaxrs_analyzer.model.JavaUtils;
import com.sebastian_daschner.jaxrs_analyzer.model.elements.HttpResponse;
import com.sebastian_daschner.jaxrs_analyzer.model.rest.MethodParameter;
import com.sebastian_daschner.jaxrs_analyzer.model.rest.ResourceMethod;
import com.sebastian_daschner.jaxrs_analyzer.model.rest.Resources;
import com.sebastian_daschner.jaxrs_analyzer.model.rest.Response;
import com.sebastian_daschner.jaxrs_analyzer.model.results.ClassResult;
import com.sebastian_daschner.jaxrs_analyzer.model.results.MethodResult;
import com.sebastian_daschner.jaxrs_analyzer.utils.StringUtils;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.Doc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.ParamTag;
import java.util.Optional;
import java.util.Set;
import static com.sebastian_daschner.jaxrs_analyzer.analysis.results.JavaDocParameterResolver.*;
/**
* Interprets the analyzed project results to REST results.
*
* @author Sebastian Daschner
*/
public class ResultInterpreter {
private static final String DEPRECATED_TAG_NAME = "@deprecated";
private JavaTypeAnalyzer javaTypeAnalyzer;
private Resources resources;
private DynamicTypeAnalyzer dynamicTypeAnalyzer;
private StringParameterResolver stringParameterResolver;
/**
* Interprets the class results.
*
* @return All REST resources
*/
public Resources interpret(final Set<ClassResult> classResults) {
resources = new Resources();
resources.setBasePath(PathNormalizer.getApplicationPath(classResults));
javaTypeAnalyzer = new JavaTypeAnalyzer(resources.getTypeRepresentations());
dynamicTypeAnalyzer = new DynamicTypeAnalyzer(resources.getTypeRepresentations());
stringParameterResolver = new StringParameterResolver(resources.getTypeRepresentations(), javaTypeAnalyzer);
classResults.stream().filter(c -> c.getResourcePath() != null).forEach(this::interpretClassResult);
return resources;
}
/**
* Interprets the class result.
*
* @param classResult The class result
*/
private void interpretClassResult(final ClassResult classResult) {
classResult.getMethods().forEach(m -> interpretMethodResult(m, classResult));
}
/**
* Interprets the method result.
*
* @param methodResult The method result
* @param classResult The result of the containing class
*/
private void interpretMethodResult(final MethodResult methodResult, final ClassResult classResult) {
if (methodResult.getSubResource() != null) {
interpretClassResult(methodResult.getSubResource());
return;
}
// determine resource of the method
final String path = PathNormalizer.getPath(methodResult);
final ResourceMethod resourceMethod = interpretResourceMethod(methodResult, classResult);
resources.addMethod(path, resourceMethod);
}
/**
* Interprets the result of a resource method.
*
* @param methodResult The method result
* @param classResult The result of the containing class
* @return The resource method which this method represents
*/
private ResourceMethod interpretResourceMethod(final MethodResult methodResult, final ClassResult classResult) {
final MethodDoc methodDoc = methodResult.getMethodDoc();
final String description = methodDoc == null || StringUtils.isBlank(methodDoc.commentText()) ? null : methodDoc.commentText();
final ResourceMethod resourceMethod = new ResourceMethod(methodResult.getHttpMethod(), description);
updateMethodParameters(resourceMethod.getMethodParameters(), classResult.getClassFields());
updateMethodParameters(resourceMethod.getMethodParameters(), methodResult.getMethodParameters());
addParameterDescriptions(resourceMethod.getMethodParameters(), methodDoc);
stringParameterResolver.replaceParametersTypes(resourceMethod.getMethodParameters());
if (methodResult.getRequestBodyType() != null) {
resourceMethod.setRequestBody(javaTypeAnalyzer.analyze(methodResult.getRequestBodyType()));
resourceMethod.setRequestBodyDescription(findRequestBodyDescription(methodDoc));
}
// add default status code due to JSR 339
addDefaultResponses(methodResult);
methodResult.getResponses().forEach(r -> interpretResponse(r, resourceMethod));
addMediaTypes(methodResult, classResult, resourceMethod);
if (methodResult.isDeprecated() || classResult.isDeprecated() || hasDeprecationTag(methodDoc))
resourceMethod.setDeprecated(true);
return resourceMethod;
}
private boolean hasDeprecationTag(MethodDoc doc) {
if (doc == null)
return false;
return hasMethodDeprecationTag(doc) || hasClassDeprecationTag(doc);
}
private boolean hasMethodDeprecationTag(MethodDoc doc) {
return doc.tags(DEPRECATED_TAG_NAME).length > 0;
}
private boolean hasClassDeprecationTag(MethodDoc methodDoc) {
final ClassDoc doc = methodDoc.containingClass();
return doc != null && doc.tags(DEPRECATED_TAG_NAME).length > 0;
}
private void addParameterDescriptions(final Set<MethodParameter> methodParameters, final MethodDoc methodDoc) {
if (methodDoc == null)
return;
methodParameters.forEach(p -> {
final Optional<ParamTag> tag = findParameterDoc(p, methodDoc);
final String description = tag.map(ParamTag::parameterComment)
.orElseGet(() -> findFieldDoc(p, methodDoc.containingClass())
.map(Doc::commentText).orElse(null));
p.setDescription(description);
});
}
private String findRequestBodyDescription(final MethodDoc methodDoc) {
if (methodDoc == null)
return null;
return findRequestBodyDoc(methodDoc).map(ParamTag::parameterComment).orElse(null);
}
/**
* Updates {@code parameters} to contain the {@code additional} parameters as well.
* Preexisting parameters with identical names are overridden.
*/
private void updateMethodParameters(final Set<MethodParameter> parameters, final Set<MethodParameter> additional) {
additional.forEach(a -> {
// remove preexisting parameters with identical names
final Optional<MethodParameter> existingParameter = parameters.stream().filter(p -> p.getName().equals(a.getName())).findAny();
existingParameter.ifPresent(parameters::remove);
parameters.add(a);
});
}
private void addDefaultResponses(final MethodResult methodResult) {
if (methodResult.getResponses().isEmpty()) {
final HttpResponse httpResponse = new HttpResponse();
httpResponse.getStatuses().add(javax.ws.rs.core.Response.Status.NO_CONTENT.getStatusCode());
methodResult.getResponses().add(httpResponse);
return;
}
methodResult.getResponses().stream().filter(r -> r.getStatuses().isEmpty())
.forEach(r -> r.getStatuses().add(javax.ws.rs.core.Response.Status.OK.getStatusCode()));
}
private void interpretResponse(final HttpResponse httpResponse, final ResourceMethod method) {
method.getResponseMediaTypes().addAll(httpResponse.getContentTypes());
httpResponse.getStatuses().forEach(s -> {
Response response = httpResponse.getInlineEntities().stream().findAny()
.map(JsonMapper::map).map(dynamicTypeAnalyzer::analyze).map(Response::new).orElse(null);
if (response == null) {
// no inline entities -> potential class type will be considered
response = httpResponse.getEntityTypes().isEmpty() ? new Response() :
new Response(javaTypeAnalyzer.analyze(JavaUtils.determineMostSpecificType(httpResponse.getEntityTypes().stream().toArray(String[]::new))));
}
response.getHeaders().addAll(httpResponse.getHeaders());
method.getResponses().put(s, response);
});
}
/**
* Adds the request and response media type information to the resource method.
*
* @param methodResult The method result
* @param classResult The class result
* @param resourceMethod The resource method
*/
private void addMediaTypes(final MethodResult methodResult, final ClassResult classResult, final ResourceMethod resourceMethod) {
// accept media types -> inherit
resourceMethod.getRequestMediaTypes().addAll(methodResult.getRequestMediaTypes());
if (resourceMethod.getRequestMediaTypes().isEmpty()) {
resourceMethod.getRequestMediaTypes().addAll(classResult.getRequestMediaTypes());
}
// response media types -> use annotations if not yet present
if (resourceMethod.getResponseMediaTypes().isEmpty())
resourceMethod.getResponseMediaTypes().addAll(methodResult.getResponseMediaTypes());
// -> inherit
if (resourceMethod.getResponseMediaTypes().isEmpty()) {
resourceMethod.getResponseMediaTypes().addAll(classResult.getResponseMediaTypes());
}
}
}