/* * Copyright 2016 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.thrift; import java.io.IOException; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.stream.Collectors; import java.util.stream.Stream; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.MoreObjects; import com.google.common.base.Strings; import com.google.common.collect.ImmutableMap; import com.linecorp.armeria.server.docs.DocStringExtractor; /** * {@link ThriftDocStringExtractor} is a DocString extractor for Thrift IDL JSON. * * <p>To include docstrings in {@link com.linecorp.armeria.server.docs.DocService} pages, use a recent * development version of the Thrift compiler (0.9.3 will not work) and compile your thrift files with 'json' * code generation and include the resulting json files in the classpath location * {@code META-INF/armeria/thrift}. The classpath location can be changed by setting the * {@code com.linecorp.armeria.thrift.jsonDir} system property. */ final class ThriftDocStringExtractor extends DocStringExtractor { private static final TypeReference<HashMap<String, Object>> JSON_VALUE_TYPE = new TypeReference<HashMap<String, Object>>() {}; private static final String FQCN_DELIM = "."; private static final String DELIM = "/"; ThriftDocStringExtractor() { super("META-INF/armeria/thrift", "com.linecorp.armeria.thrift.jsonDir"); } @Override protected boolean acceptFile(String filename) { return filename.endsWith(".json"); } /** * Gets the namespace key of names. * @param names name list. * @return merged key. */ static String key(String... names) { return Stream.of(names).filter(s -> !Strings.isNullOrEmpty(s)).collect(Collectors.joining(DELIM)); } @Override protected Map<String, String> getDocStringsFromFiles(Map<String, byte[]> files) { ImmutableMap.Builder<String, String> docStrings = ImmutableMap.builder(); for (byte[] file : files.values()) { try { final Map<String, Object> json = new ObjectMapper().readValue(file, JSON_VALUE_TYPE); @SuppressWarnings("unchecked") final Map<String, Object> namespaces = (Map<String, Object>) json.getOrDefault("namespaces", ImmutableMap.of()); final String packageName = (String) namespaces.get("java"); json.forEach((key, children) -> { if (children instanceof Collection) { @SuppressWarnings("unchecked") Collection<Object> castChildren = (Collection<Object>) children; castChildren.forEach( grandChild -> traverseChildren(docStrings, packageName, FQCN_DELIM, grandChild)); } }); } catch (IOException e) { throw new IllegalStateException(e); } } return docStrings.build(); } private static void traverseChildren(ImmutableMap.Builder<String, String> docStrings, String prefix, String delimiter, Object node) { if (node instanceof Map) { @SuppressWarnings("unchecked") final Map<String, Object> map = (Map<String, Object>) node; final String name = (String) map.get("name"); final String doc = (String) map.get("doc"); String childPrefix; if (name != null) { childPrefix = MoreObjects.firstNonNull(prefix, "") + delimiter + name; if (doc != null) { docStrings.put(childPrefix, doc.trim()); } } else { childPrefix = prefix; } map.forEach((key, value) -> traverseChildren(docStrings, childPrefix, DELIM, value)); } else if (node instanceof Iterable) { @SuppressWarnings("unchecked") final Iterable<Object> children = (Iterable<Object>) node; children.forEach(child -> traverseChildren(docStrings, prefix, DELIM, child)); } } }