/*
* Copyright 2012 Guido Steinacker
*
* 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 de.otto.jsonhome.generator;
import de.otto.jsonhome.annotation.Doc;
import de.otto.jsonhome.annotation.Docs;
import de.otto.jsonhome.model.Documentation;
import org.markdown4j.Markdown4jProcessor;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URI;
import java.util.ArrayList;
import java.util.List;
import static de.otto.jsonhome.generator.UriBuilder.normalized;
import static de.otto.jsonhome.model.Documentation.documentation;
import static de.otto.jsonhome.model.Documentation.emptyDocs;
import static java.net.URI.create;
import static java.util.Arrays.asList;
import static org.springframework.core.annotation.AnnotationUtils.findAnnotation;
/**
* A generator used to create {@link Documentation} for a link-relation type or href-var.
*
* @author Guido Steinacker
* @since 11.10.12
*/
public class DocsGenerator {
private final URI relationTypeBaseUri;
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
private Resource rootDir;
/**
* Creates a DocsGenerator.
*
* @param relationTypeBaseUri base URI of all relation types.
* @param docRootDir root of the Markdown documents. May be null if not used.
*/
public DocsGenerator(final URI relationTypeBaseUri, final String docRootDir) {
this.rootDir = docRootDir != null ? new ClassPathResource(docRootDir) : null;
this.relationTypeBaseUri = normalized(relationTypeBaseUri).toUri();
}
/**
* Returns the Documentation for a single link-relation type supported by the controller.
*
* This implementation is parsing the {@link Doc} and {@link Docs} annotations of the controller.
*
* @param relationType URI of the link-relation type.
* @param controller the controller, possibly annotated with Docs or Doc.
* @return Documentation, possibly empty but never null.
*/
public Documentation documentationFrom(final URI relationType, final Class<?> controller) {
Docs docs = findAnnotation(controller, Docs.class);
if (docs != null) {
for (final Doc relDoc : docs.value()) {
if (!relDoc.rel().isEmpty() && absoluteUriOf(relDoc.rel()).equals(relationType)) {
return documentationFrom(relDoc);
}
}
} else {
Doc relDoc = findAnnotation(controller, Doc.class);
if (relDoc != null && !relDoc.rel().isEmpty() && absoluteUriOf(relDoc.rel()).equals(relationType)) {
return documentationFrom(relDoc);
}
}
return emptyDocs();
}
/**
* Returns the documentation of a single href parameter.
*
* This implementation is using the optional {@link Doc} annotation of the parameter to get the documentation.
*
* @param parameterInfo information about the href-var parameter.
* @return Documentation, possibly empty but never null.
*/
public Documentation documentationFor(final ParameterInfo parameterInfo) {
if (parameterInfo.hasAnnotation(Doc.class)) {
Doc doc = parameterInfo.getAnnotation(Doc.class);
return documentationFrom(doc);
} else {
return emptyDocs();
}
}
/**
* Creates a Documentation instance from a Doc annotation.
* @param doc the annotation.
* @return Documentation, possibly empty but never null.
*/
private Documentation documentationFrom(final Doc doc) {
final String link = doc.link();
final List<String> values = asList(doc.value());
final String detailedDescription = (doc.include() != null && !doc.include().isEmpty())
? htmlFromMarkdown(doc.include())
: "";
return documentation(
removeEmptyStringsAndNullValuesFrom(values),
detailedDescription,
absoluteUriOf(link)
);
}
/**
* Constructs an absolute URI.
*
* If the link is already absolute, URI.create(link) is returned. Otherwise, the {@link #relationTypeBaseUri}
* is used to create the URI.
* @param link absolute or relative relation-type URI.
* @return absolute URI, uniquely identifying a link-relation type.
*/
private URI absoluteUriOf(final String link) {
if (link != null && !link.isEmpty()) {
if (link.startsWith("http://")) {
return create(link);
} else {
final String baseUri = relationTypeBaseUri.toString();
if (baseUri.endsWith("/") || link.startsWith("/")) {
return create(baseUri + link);
} else {
return create(baseUri + "/" + link);
}
}
} else {
return null;
}
}
private String htmlFromMarkdown(final String path) {
try {
if (rootDir != null) {
final InputStream file = rootDir.createRelative(path).getInputStream();
try {
final BufferedReader reader = new BufferedReader( new InputStreamReader(file));
String line;
final StringBuilder markdown = new StringBuilder();
while((line = reader.readLine()) != null) {
markdown.append(line);
markdown.append(LINE_SEPARATOR);
}
return new Markdown4jProcessor().process(markdown.toString()).trim();
} finally {
file.close();
}
} else {
return null;
}
} catch (final IOException e) {
// TODO: log exception + proceed.
return null;
}
}
private List<String> removeEmptyStringsAndNullValuesFrom(final List<String> list) {
final List<String> result = new ArrayList<>();
for (final String s : list) {
if (s != null && !s.isEmpty()) {
result.add(s);
}
}
return result;
}
}