Java tutorial
/** * Apache License * Version 2.0, January 2004 * http://www.apache.org/licenses/ * * TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION * * 1. Definitions. * * "License" shall mean the terms and conditions for use, reproduction, * and distribution as defined by Sections 1 through 9 of this document. * * "Licensor" shall mean the copyright owner or entity authorized by * the copyright owner that is granting the License. * * "Legal Entity" shall mean the union of the acting entity and all * other entities that control, are controlled by, or are under common * control with that entity. For the purposes of this definition, * "control" means (i) the power, direct or indirect, to cause the * direction or management of such entity, whether by contract or * otherwise, or (ii) ownership of fifty percent (50%) or more of the * outstanding shares, or (iii) beneficial ownership of such entity. * * "You" (or "Your") shall mean an individual or Legal Entity * exercising permissions granted by this License. * * "Source" form shall mean the preferred form for making modifications, * including but not limited to software source code, documentation * source, and configuration files. * * "Object" form shall mean any form resulting from mechanical * transformation or translation of a Source form, including but * not limited to compiled object code, generated documentation, * and conversions to other media types. * * "Work" shall mean the work of authorship, whether in Source or * Object form, made available under the License, as indicated by a * copyright notice that is included in or attached to the work * (an example is provided in the Appendix below). * * "Derivative Works" shall mean any work, whether in Source or Object * form, that is based on (or derived from) the Work and for which the * editorial revisions, annotations, elaborations, or other modifications * represent, as a whole, an original work of authorship. For the purposes * of this License, Derivative Works shall not include works that remain * separable from, or merely link (or bind by name) to the interfaces of, * the Work and Derivative Works thereof. * * "Contribution" shall mean any work of authorship, including * the original version of the Work and any modifications or additions * to that Work or Derivative Works thereof, that is intentionally * submitted to Licensor for inclusion in the Work by the copyright owner * or by an individual or Legal Entity authorized to submit on behalf of * the copyright owner. For the purposes of this definition, "submitted" * means any form of electronic, verbal, or written communication sent * to the Licensor or its representatives, including but not limited to * communication on electronic mailing lists, source code control systems, * and issue tracking systems that are managed by, or on behalf of, the * Licensor for the purpose of discussing and improving the Work, but * excluding communication that is conspicuously marked or otherwise * designated in writing by the copyright owner as "Not a Contribution." * * "Contributor" shall mean Licensor and any individual or Legal Entity * on behalf of whom a Contribution has been received by Licensor and * subsequently incorporated within the Work. * * 2. Grant of Copyright License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * copyright license to reproduce, prepare Derivative Works of, * publicly display, publicly perform, sublicense, and distribute the * Work and such Derivative Works in Source or Object form. * * 3. Grant of Patent License. Subject to the terms and conditions of * this License, each Contributor hereby grants to You a perpetual, * worldwide, non-exclusive, no-charge, royalty-free, irrevocable * (except as stated in this section) patent license to make, have made, * use, offer to sell, sell, import, and otherwise transfer the Work, * where such license applies only to those patent claims licensable * by such Contributor that are necessarily infringed by their * Contribution(s) alone or by combination of their Contribution(s) * with the Work to which such Contribution(s) was submitted. If You * institute patent litigation against any entity (including a * cross-claim or counterclaim in a lawsuit) alleging that the Work * or a Contribution incorporated within the Work constitutes direct * or contributory patent infringement, then any patent licenses * granted to You under this License for that Work shall terminate * as of the date such litigation is filed. * * 4. Redistribution. You may reproduce and distribute copies of the * Work or Derivative Works thereof in any medium, with or without * modifications, and in Source or Object form, provided that You * meet the following conditions: * * (a) You must give any other recipients of the Work or * Derivative Works a copy of this License; and * * (b) You must cause any modified files to carry prominent notices * stating that You changed the files; and * * (c) You must retain, in the Source form of any Derivative Works * that You distribute, all copyright, patent, trademark, and * attribution notices from the Source form of the Work, * excluding those notices that do not pertain to any part of * the Derivative Works; and * * (d) If the Work includes a "NOTICE" text file as part of its * distribution, then any Derivative Works that You distribute must * include a readable copy of the attribution notices contained * within such NOTICE file, excluding those notices that do not * pertain to any part of the Derivative Works, in at least one * of the following places: within a NOTICE text file distributed * as part of the Derivative Works; within the Source form or * documentation, if provided along with the Derivative Works; or, * within a display generated by the Derivative Works, if and * wherever such third-party notices normally appear. The contents * of the NOTICE file are for informational purposes only and * do not modify the License. You may add Your own attribution * notices within Derivative Works that You distribute, alongside * or as an addendum to the NOTICE text from the Work, provided * that such additional attribution notices cannot be construed * as modifying the License. * * You may add Your own copyright statement to Your modifications and * may provide additional or different license terms and conditions * for use, reproduction, or distribution of Your modifications, or * for any such Derivative Works as a whole, provided Your use, * reproduction, and distribution of the Work otherwise complies with * the conditions stated in this License. * * 5. Submission of Contributions. Unless You explicitly state otherwise, * any Contribution intentionally submitted for inclusion in the Work * by You to the Licensor shall be under the terms and conditions of * this License, without any additional terms or conditions. * Notwithstanding the above, nothing herein shall supersede or modify * the terms of any separate license agreement you may have executed * with Licensor regarding such Contributions. * * 6. Trademarks. This License does not grant permission to use the trade * names, trademarks, service marks, or product names of the Licensor, * except as required for reasonable and customary use in describing the * origin of the Work and reproducing the content of the NOTICE file. * * 7. Disclaimer of Warranty. Unless required by applicable law or * agreed to in writing, Licensor provides the Work (and each * Contributor provides its Contributions) on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied, including, without limitation, any warranties or conditions * of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A * PARTICULAR PURPOSE. You are solely responsible for determining the * appropriateness of using or redistributing the Work and assume any * risks associated with Your exercise of permissions under this License. * * 8. Limitation of Liability. In no event and under no legal theory, * whether in tort (including negligence), contract, or otherwise, * unless required by applicable law (such as deliberate and grossly * negligent acts) or agreed to in writing, shall any Contributor be * liable to You for damages, including any direct, indirect, special, * incidental, or consequential damages of any character arising as a * result of this License or out of the use or inability to use the * Work (including but not limited to damages for loss of goodwill, * work stoppage, computer failure or malfunction, or any and all * other commercial damages or losses), even if such Contributor * has been advised of the possibility of such damages. * * 9. Accepting Warranty or Additional Liability. While redistributing * the Work or Derivative Works thereof, You may choose to offer, * and charge a fee for, acceptance of support, warranty, indemnity, * or other liability obligations and/or rights consistent with this * License. However, in accepting such obligations, You may act only * on Your own behalf and on Your sole responsibility, not on behalf * of any other Contributor, and only if You agree to indemnify, * defend, and hold each Contributor harmless for any liability * incurred by, or claims asserted against, such Contributor by reason * of your accepting any such warranty or additional liability. * * END OF TERMS AND CONDITIONS * * APPENDIX: How to apply the Apache License to your work. * * To apply the Apache License to your work, attach the following * boilerplate notice, with the fields enclosed by brackets "{}" * replaced with your own identifying information. (Don't include * the brackets!) The text should be enclosed in the appropriate * comment syntax for the file format. We also recommend that a * file or class name and description of purpose be included on the * same "printed page" as the copyright notice for easier * identification within third-party archives. * * Copyright 2014 Edgar Espina * * 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.jooby.internal.apitool; import com.fasterxml.jackson.databind.BeanDescription; import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; import com.google.common.base.Splitter; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.Maps; import com.google.inject.TypeLiteral; import com.google.inject.internal.MoreTypes; import io.swagger.converter.ModelConverter; import io.swagger.converter.ModelConverterContext; import io.swagger.converter.ModelConverters; import io.swagger.jackson.AbstractModelConverter; import io.swagger.models.Model; import io.swagger.models.Operation; import io.swagger.models.Path; import io.swagger.models.Response; import io.swagger.models.Swagger; import io.swagger.models.Tag; import io.swagger.models.parameters.AbstractSerializableParameter; import io.swagger.models.parameters.BodyParameter; import io.swagger.models.parameters.FormParameter; import io.swagger.models.parameters.HeaderParameter; import io.swagger.models.parameters.Parameter; import io.swagger.models.parameters.PathParameter; import io.swagger.models.parameters.QueryParameter; import io.swagger.models.parameters.SerializableParameter; import io.swagger.models.properties.ArrayProperty; import io.swagger.models.properties.FileProperty; import io.swagger.models.properties.Property; import io.swagger.models.properties.PropertyBuilder; import io.swagger.models.properties.PropertyBuilder.PropertyId; import io.swagger.models.properties.RefProperty; import io.swagger.util.Json; import io.swagger.util.Yaml; import org.jooby.MediaType; import org.jooby.Upload; import org.jooby.apitool.RouteMethod; import org.jooby.apitool.RouteParameter; import org.jooby.apitool.RouteResponse; import java.lang.annotation.Annotation; import java.lang.reflect.Array; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.EnumMap; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; public class SwaggerBuilder { static { /** Convert Upload to Swagger FileProperty: */ ModelConverters.getInstance().addConverter(new AbstractModelConverter(Json.mapper()) { @Override public Property resolveProperty(Type type, ModelConverterContext context, Annotation[] annotations, Iterator<ModelConverter> chain) { try { TypeLiteral<?> typeLiteral = TypeLiteral.get(type); String typeName = typeLiteral.getType().getTypeName(); if (typeName.equals("java.util.List<org.jooby.Upload>") || typeName.equals("java.util.Set<org.jooby.Upload>")) { return new ArrayProperty(new FileProperty()); } if (typeName.equals(Upload.class.getName())) { return new FileProperty(); } return super.resolveProperty(type, context, annotations, chain); } catch (IllegalArgumentException x) { // shhh return super.resolveProperty(type, context, annotations, chain); } } }); /** Kotlin module? */ try { Module module = (Module) SwaggerBuilder.class.getClassLoader() .loadClass("com.fasterxml.jackson.module.kotlin.KotlinModule").newInstance(); Json.mapper().registerModule(module); Yaml.mapper().registerModule(module); } catch (ClassNotFoundException | InstantiationException | IllegalAccessException x) { // Nop } /** Java8/Optional: */ Jdk8Module jdk8 = new Jdk8Module(); Json.mapper().registerModule(jdk8); Yaml.mapper().registerModule(jdk8); } private static class ResponseWithStatusCode { String statusCode; Response response; public ResponseWithStatusCode(String statusCode, Response response) { this.statusCode = statusCode; this.response = response; } } private Function<RouteMethod, String> tagger; public SwaggerBuilder(Function<RouteMethod, String> tagger) { this.tagger = tagger; } public Swagger build(Swagger base, final List<RouteMethod> routes) throws Exception { Swagger swagger = Optional.ofNullable(base).orElseGet(Swagger::new); /** Tags: */ Function<String, Tag> tagFactory = value -> Optional.ofNullable(swagger.getTag(value)).orElseGet(() -> { Tag tag = new Tag().name(value); swagger.addTag(tag); return tag; }); /** Paths: */ Function<String, Path> pathFactory = pattern -> Optional.ofNullable(swagger.getPath(pattern)) .orElseGet(() -> { Path path = new Path(); swagger.path(pattern, path); return path; }); ModelConverters converter = ModelConverters.getInstance(); /** Model factory: */ Function<Type, Model> modelFactory = type -> { for (Map.Entry<String, Model> entry : converter.readAll(type).entrySet()) { swagger.addDefinition(entry.getKey(), doModel(type, entry.getValue())); } // reference: Property property = converter.readAsProperty(type); Map<PropertyId, Object> args = new EnumMap<>(PropertyId.class); return PropertyBuilder.toModel(PropertyBuilder.merge(property, args)); }; Map<String, Integer> opIds = new HashMap<>(); for (RouteMethod route : routes) { /** Find or create tag: */ List<Tag> tags = tags(route, tagFactory); /** Find or create path: */ Path path = pathFactory.apply(route.pattern()); /** Operation: */ Operation op = new Operation(); tags.forEach(it -> op.addTag(it.getName())); op.operationId(operationId(route, tags.get(0), opIds)); /** Doc and summary: */ op.summary(summary(route)); op.description(description(route, op.getSummary())); /** Consumes/Produces . */ consumes(route, op::addConsumes); produces(route, op::addProduces); /** Parameters: */ route.parameters().stream().flatMap(it -> { Type type = it.type(); final Property property = converter.readAsProperty(type); Parameter parameter = it.accept(new RouteParameter.Visitor<Parameter>() { @Override public Parameter visitBody(final RouteParameter parameter) { return new BodyParameter().schema(modelFactory.apply(parameter.type())); } @Override public Parameter visitFile(final RouteParameter parameter) { return complement(property, parameter, new FormParameter()); } @Override public Parameter visitForm(final RouteParameter parameter) { return complement(property, parameter, new FormParameter()); } @Override public Parameter visitHeader(final RouteParameter parameter) { return complement(property, parameter, new HeaderParameter()); } @Override public Parameter visitPath(final RouteParameter parameter) { return complement(property, parameter, new PathParameter()); } @Override public Parameter visitQuery(final RouteParameter parameter) { return complement(property, parameter, new QueryParameter()); } }); if (it.kind() == RouteParameter.Kind.FILE) { op.setConsumes(ImmutableList.of(MediaType.multipart.name())); } else if (it.kind() == RouteParameter.Kind.FORM) { op.setConsumes(ImmutableList.of(MediaType.form.name(), MediaType.multipart.name())); } if (property instanceof RefProperty && (it.kind() == RouteParameter.Kind.QUERY || it.kind() == RouteParameter.Kind.FORM)) { return expandParameter(converter, it, it.type(), it.optional()).stream(); } else { parameter.setName(it.name()); parameter.setRequired(!it.optional()); parameter.setDescription(property.getDescription()); it.description().ifPresent(parameter::setDescription); return Stream.of(parameter); } }).forEach(op::addParameter); /** Response: */ buildResponse(route, modelFactory, rsp -> op.addResponse(rsp.statusCode, rsp.response)); /** Done: */ path.set(method(route), op); } Function<Function<RouteMethod, List<String>>, List<String>> mediaTypes = types -> routes.stream() .flatMap(it -> types.apply(it).stream()).collect(Collectors.toList()); /** Default consumes/produces: */ List<String> consumes = mediaTypes.apply(RouteMethod::consumes); if (consumes.size() == 0) { swagger.consumes(MediaType.json.name()); } else if (consumes.size() == 1) { swagger.consumes(consumes); } List<String> produces = mediaTypes.apply(RouteMethod::produces); if (produces.size() == 0) { swagger.produces(MediaType.json.name()); } else if (produces.size() == 1) { swagger.produces(produces); } return swagger; } private void buildResponse(RouteMethod route, Function<Type, Model> modelFactory, Consumer<ResponseWithStatusCode> consumer) { Object[] apiResponses = (Object[]) route.attributes().get("apiResponses"); if (apiResponses != null) { /** ApiResponses annotation: */ Set<Integer> codes = new HashSet<>(); Stream.of(apiResponses).map(it -> buildResponse(modelFactory, (Map) it, c -> !codes.add(c))) .forEach(consumer); } else { Integer code = (Integer) route.attributes().get("ApiResponse.code"); if (code != null) { /** Single ApiResponse annotation: */ consumer.accept(buildResponse(modelFactory, route.attributes(), c -> false)); } else { /** Default response: */ RouteResponse returns = route.response(); Map<Integer, String> status = returns.status(); Integer statusCode = (Integer) route.attributes().getOrDefault("ApiOperation.code", returns.statusCode()); Response response = new Response(); String doc = returns.description().orElseGet(() -> FriendlyTypeName.name(returns.type())); response.description(doc); Type responseType = (Type) route.attributes().getOrDefault("ApiOperation.response", returns.type()); if (!isVoid(responseType)) { response.responseSchema(modelFactory.apply(responseType)); } buildResponseHeader(route.attributes(), "ApiOperation.responseHeaders", response::addHeader); consumer.accept(new ResponseWithStatusCode(statusCode.toString(), response)); status.entrySet().stream().filter(it -> !statusCode.equals(it.getKey())) .forEach(it -> consumer.accept(new ResponseWithStatusCode(it.getKey().toString(), new Response().description(it.getValue())))); } } } private boolean isVoid(Type type) { return "void".equals(type.getTypeName()) || Void.class.getName().equals(type.getTypeName()); } private ResponseWithStatusCode buildResponse(Function<Type, Model> modelFactory, Map<String, Object> attributes, Predicate<Integer> statusCode) { Response response = new Response(); String description = (String) attributes.get("ApiResponse.message"); Class type = (Class) attributes.get("ApiResponse.response"); if (!isVoid(type)) { response.setResponseSchema(modelFactory.apply(type)); } response.setDescription(description); buildResponseHeader(attributes, "ApiResponse.responseHeaders", response::addHeader); Integer code = (Integer) attributes.get("ApiResponse.code"); String key = code.toString(); if (statusCode.test(code)) { key += "(" + type.getSimpleName() + ")"; } return new ResponseWithStatusCode(key, response); } private void buildResponseHeader(Map<String, Object> attributes, String name, BiConsumer<String, Property> consumer) { Object[] headers = (Object[]) attributes.get(name); if (headers != null) { ModelConverters converter = ModelConverters.getInstance(); Stream.of(headers).map(Map.class::cast) .filter(it -> ((String) it.get("ResponseHeader.name")).length() > 0).forEach(header -> { String hname = header.get("ResponseHeader.name").toString(); Property htype = converter.readAsProperty((Type) header.get("ResponseHeader.response")); consumer.accept(hname, htype); }); } } private String method(RouteMethod route) { String method = stringAttribute(route, "httpMethod"); if (method == null) { method = route.method(); } return method.toLowerCase(); } private void consumes(RouteMethod route, Consumer<String> consumer) { String consumes = stringAttribute(route, "consumes"); if (consumes == null) { route.consumes().forEach(consumer::accept); } else { Stream.of(consumes.split(",")).forEach(consumer); } } private void produces(RouteMethod route, Consumer<String> consumer) { String produces = stringAttribute(route, "produces"); if (produces == null) { route.produces().forEach(consumer::accept); } else { Stream.of(produces.split(",")).forEach(consumer); } } private String description(RouteMethod route, String summary) { String notes = stringAttribute(route, "notes"); if (notes == null) { return route.description().map(description -> { String prefix = Optional.ofNullable(summary).orElse(""); return description.replace(prefix + ".", ""); }).orElse(null); } return notes; } private String summary(RouteMethod route) { String summary = stringAttribute(route, "apiOperation"); if (summary == null) { return route.description().map(description -> { int dot = description.indexOf('.'); if (dot > 0) { return description.substring(0, dot); } return null; }).orElse(null); } return summary; } private String operationId(RouteMethod route, Tag tag, Map<String, Integer> opIds) { String operationId = stringAttribute(route, "nickname"); if (operationId == null) { operationId = route.name().orElseGet(() -> { String args = ""; if (route.method().equals("GET") && route.parameters().size() > 0) { args = route.parameters().stream() .filter(p -> p.kind() == RouteParameter.Kind.QUERY || p.kind() == RouteParameter.Kind.PATH) .map(p -> p.name()).map(this::ucase) .reduce(new StringBuilder("By"), StringBuilder::append, StringBuilder::append) .toString(); } return route.method().toLowerCase() + ucase(tag.getName().replace("/", "")) + args; }); } int c = opIds.getOrDefault(operationId, 0); opIds.put(operationId, c + 1); if (c == 0) { return operationId; } return operationId + c; } private List<Tag> tags(RouteMethod route, Function<String, Tag> tagFactory) { String[] tags = arrayAttribute(route, "tags"); if (tags != null) { List<Tag> result = Stream.of(tags).filter(it -> it.length() > 0).map(tagFactory) .collect(Collectors.toList()); if (result.size() > 0) { return result; } } Tag tag = tagFactory.apply(tagger.apply(route)); route.summary().ifPresent(tag::description); return ImmutableList.of(tag); } private String stringAttribute(RouteMethod route, String name) { return (String) swaggerAttribute(route, name, it -> it.toString().length() > 0); } private <T> T[] arrayAttribute(RouteMethod routeMethod, String name) { return (T[]) swaggerAttribute(routeMethod, "tags", v -> Array.getLength(v) > 0); } private Object swaggerAttribute(RouteMethod route, String name, Predicate<Object> filter) { String[] prefix = { "ApiOperation", "swagger" }; for (String p : prefix) { String key = p.equalsIgnoreCase(name) ? name : p + "." + name; Object value = route.attributes().get(key); if (value != null && filter.test(value)) { return value; } } return null; } private void apiOperation(Map<String, Object> attributes, Operation op, Response response, Function<Type, Model> modelFactory, Function<String, Tag> tagFactory) { attributes.forEach((name, value) -> { switch (name) { case "ApiOperation": String summary = Strings.emptyToNull((String) value); if (summary != null) { op.summary(summary); } break; case "tags": Stream.of((String[]) value).filter(it -> it != null && it.trim().length() > 0).map(tagFactory) .forEach(tag -> op.addTag(tag.getName())); break; case "response": Class responseType = (Class) value; if (responseType != Void.class) { response.responseSchema(modelFactory.apply(responseType)); } break; case "nickname": String operationId = Strings.emptyToNull((String) value); if (operationId != null) { op.setOperationId(operationId); } break; case "produces": String produces = Strings.emptyToNull((String) value); if (produces != null) { op.produces(Splitter.on(",").trimResults().omitEmptyStrings().splitToList(produces)); } break; case "consumes": String consumes = Strings.emptyToNull((String) value); if (consumes != null) { op.consumes(Splitter.on(",").trimResults().omitEmptyStrings().splitToList(consumes)); } break; case "code": int code = (Integer) value; if (code != 200) { op.addResponse(value.toString(), response); } break; case "responseHeaders": break; } }); } private Map<String, Object> swaggerAttributes(Class annotation, Map<String, Object> attributes) { String name = annotation.getSimpleName(); return attributes.entrySet().stream() .filter(it -> it.getKey().equalsIgnoreCase(name) || it.getKey().startsWith(name + ".")) .filter(it -> { Object value = it.getValue(); if (value instanceof String) { return !Strings.isNullOrEmpty(((String) value).trim()); } return value != null; }).map(e -> { String key = e.getKey(); Object value = attributes.get(key); if (key.equalsIgnoreCase(name)) { return Maps.immutableEntry(name, value); } return Maps.immutableEntry(key.replace("ApiOperation.", ""), value); }).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); } private List<Parameter> expandParameter(ModelConverters converter, RouteParameter it, Type type, boolean optional) { Supplier<SerializableParameter> factory = it.kind() == RouteParameter.Kind.FORM ? FormParameter::new : QueryParameter::new; return expandParameter(converter.readAll(type), it, factory, simpleClassName(type), "", optional); } private List<Parameter> expandParameter(Map<String, Model> models, RouteParameter it, Supplier<SerializableParameter> factory, String typeName, String prefix, boolean optional) { List<Parameter> parameters = new ArrayList<>(); Model model = models.get(typeName); Map<String, Property> properties = model.getProperties(); Optional.ofNullable(properties).ifPresent(props -> props.values().stream().flatMap(p -> { SerializableParameter result = complement(p, it, factory.get()); String name = prefix + p.getName(); if (p instanceof RefProperty) { return expandParameter(models, it, factory, ((RefProperty) p).getSimpleRef(), name + ".", optional) .stream(); } else { result.setName(name); if (optional) { result.setRequired(false); } else { result.setRequired(p.getRequired()); } result.setDescription(p.getDescription()); return Stream.of(result); } }).forEach(parameters::add)); return parameters; } private String simpleClassName(Type type) { return MoreTypes.getRawType(type).getSimpleName(); } private String ucase(String name) { if (name.length() > 0) { return Character.toUpperCase(name.charAt(0)) + name.substring(1); } return name; } /** * Mostly for kotlin null safe operator and immutable properties. * * @param type Target type. * @param model Model. * @return Input model. */ private Model doModel(Type type, Model model) { Map<String, Property> properties = model.getProperties(); if (properties != null) { BeanDescription desc = Json.mapper().getSerializationConfig() .introspect(Json.mapper().constructType(type)); for (BeanPropertyDefinition beanProperty : desc.findProperties()) { Property property = properties.get(beanProperty.getName()); if (property != null) { property.setRequired(beanProperty.isRequired()); } } } return model; } private SerializableParameter complement(Property property, RouteParameter source, SerializableParameter param) { param.setType(property.getType()); param.setFormat(property.getFormat()); // array param: if (property instanceof ArrayProperty) { param.setItems(((ArrayProperty) property).getItems()); } // enum values: List<String> enums = source.enums(); if (enums.size() > 0) { param.setEnum(enums); } // default value: if (param instanceof AbstractSerializableParameter) { ((AbstractSerializableParameter) param).setDefault(source.defaultValue()); } return param; } }