/*
* Copyright 2017 LINE Corporation
*
* LINE Corporation 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 com.linecorp.armeria.server.docs;
import static com.google.common.collect.ImmutableList.toImmutableList;
import static com.google.common.collect.ImmutableSortedSet.toImmutableSortedSet;
import static java.util.Comparator.comparing;
import static java.util.Objects.requireNonNull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import com.linecorp.armeria.common.http.HttpHeaders;
import com.linecorp.armeria.server.Service;
/**
* The specification of one or more {@link Service}s that provides their {@link ServiceInfo}s and
* {@link NamedTypeInfo}s.
*/
public final class ServiceSpecification {
/**
* Merges the specified {@link ServiceSpecification}s into one.
*/
public static ServiceSpecification merge(Iterable<ServiceSpecification> specs) {
return new ServiceSpecification(
Streams.stream(specs).flatMap(s -> s.services().stream())::iterator,
Streams.stream(specs).flatMap(s -> s.enums().stream())::iterator,
Streams.stream(specs).flatMap(s -> s.structs().stream())::iterator,
Streams.stream(specs).flatMap(s -> s.exceptions().stream())::iterator);
}
/**
* Generates a new {@link ServiceSpecification} from the specified {@link ServiceInfo}s and
* the factory {@link Function} that creates {@link NamedTypeInfo}s for the enum, struct or exception types
* referred by the specified {@link ServiceInfo}s.
*/
public static ServiceSpecification generate(
Iterable<ServiceInfo> services,
Function<TypeSignature, ? extends NamedTypeInfo> namedTypeInfoFactory) {
// Collect all named types referred by the services.
final Set<TypeSignature> namedTypes =
Streams.stream(services)
.flatMap(s -> s.findNamedTypes().stream())
.collect(toImmutableSortedSet(comparing(TypeSignature::name)));
final Map<String, EnumInfo> enums = new HashMap<>();
final Map<String, StructInfo> structs = new HashMap<>();
final Map<String, ExceptionInfo> exceptions = new HashMap<>();
generateNamedTypeInfos(namedTypeInfoFactory, enums, structs, exceptions, namedTypes);
return new ServiceSpecification(services, enums.values(), structs.values(), exceptions.values());
}
private static void generateNamedTypeInfos(
Function<TypeSignature, ? extends NamedTypeInfo> namedTypeInfoFactory,
Map<String, EnumInfo> enums, Map<String, StructInfo> structs,
Map<String, ExceptionInfo> exceptions, Set<TypeSignature> namedTypes) {
namedTypes.forEach(type -> {
final String typeName = type.name();
if (enums.containsKey(typeName) ||
structs.containsKey(typeName) ||
exceptions.containsKey(typeName)) {
return;
}
final NamedTypeInfo newInfo = namedTypeInfoFactory.apply(type);
if (newInfo instanceof EnumInfo) {
enums.put(newInfo.name(), (EnumInfo) newInfo);
} else if (newInfo instanceof StructInfo) {
structs.put(newInfo.name(), (StructInfo) newInfo);
} else if (newInfo instanceof ExceptionInfo) {
exceptions.put(newInfo.name(), (ExceptionInfo) newInfo);
} else {
throw new Error(); // Should never reach here.
}
generateNamedTypeInfos(namedTypeInfoFactory, enums, structs, exceptions, newInfo.findNamedTypes());
});
}
private final Set<ServiceInfo> services;
private final Set<EnumInfo> enums;
private final Set<StructInfo> structs;
private final Set<ExceptionInfo> exceptions;
private final List<HttpHeaders> exampleHttpHeaders;
/**
* Creates a new instance.
*/
public ServiceSpecification(Iterable<ServiceInfo> services,
Iterable<EnumInfo> enums,
Iterable<StructInfo> structs,
Iterable<ExceptionInfo> exceptions) {
this(services, enums, structs, exceptions, ImmutableList.of());
}
/**
* Creates a new instance.
*/
public ServiceSpecification(Iterable<ServiceInfo> services,
Iterable<EnumInfo> enums,
Iterable<StructInfo> structs,
Iterable<ExceptionInfo> exceptions,
Iterable<HttpHeaders> exampleHttpHeaders) {
this.services = Streams.stream(requireNonNull(services, "services"))
.collect(toImmutableSortedSet(comparing(ServiceInfo::name)));
this.enums = collectNamedTypeInfo(enums, "enums");
this.structs = collectNamedTypeInfo(structs, "structs");
this.exceptions = collectNamedTypeInfo(exceptions, "exceptions");
this.exampleHttpHeaders = Streams.stream(requireNonNull(exampleHttpHeaders, "exampleHttpHeaders"))
.map(headers -> HttpHeaders.copyOf(headers).asImmutable())
.collect(toImmutableList());
}
private static <T extends NamedTypeInfo> Set<T> collectNamedTypeInfo(Iterable<T> values, String name) {
return Streams.stream(requireNonNull(values, name))
.collect(toImmutableSortedSet(comparing(NamedTypeInfo::name)));
}
/**
* Returns the metadata about the services in this specification.
*/
@JsonProperty
public Set<ServiceInfo> services() {
return services;
}
/**
* Returns the metadata about the enums related with the services in this specification.
*/
@JsonProperty
public Set<EnumInfo> enums() {
return enums;
}
/**
* Returns the metadata about the structs related with the services in this specification.
*/
@JsonProperty
public Set<StructInfo> structs() {
return structs;
}
/**
* Returns the metadata about the exceptions related with the services in this specification.
*/
@JsonProperty
public Set<ExceptionInfo> exceptions() {
return exceptions;
}
/**
* Returns the example HTTP headers of the services in this specification.
*/
@JsonProperty
public List<HttpHeaders> exampleHttpHeaders() {
return exampleHttpHeaders;
}
}