/*
* 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;
import com.sebastian_daschner.jaxrs_analyzer.backend.Backend;
import com.sebastian_daschner.jaxrs_analyzer.backend.swagger.SwaggerOptions;
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
/**
* Inspects the information of JAX-RS classes via bytecode analysis.
*
* @author Sebastian Daschner
*/
public class Main {
private static final String DEFAULT_NAME = "project";
private static final String DEFAULT_VERSION = "0.1-SNAPSHOT";
private static final Set<Path> projectClassPaths = new HashSet<>();
private static final Set<Path> projectSourcePaths = new HashSet<>();
private static final Set<Path> classPaths = new HashSet<>();
private static final Map<String, String> attributes = new HashMap<>();
private static String name = DEFAULT_NAME;
private static String version = DEFAULT_VERSION;
private static String backendType = "swagger";
private static Path outputFileLocation;
/**
* Inspects JAX-RS projects and outputs the gathered information.
* <p>
* Argument usage: {@code [options] projectPath [projectPaths...]}
* <p>
* The {@code projectPath} entries may be directories or jar-files containing the classes to be analyzed
* <p>
* Following available options:
* <ul>
* <li>{@code -b backend} The backend to choose: {@code swagger} (default), {@code plaintext}, {@code asciidoc}</li>
* <li>{@code -cp class path[:class paths...]} The additional class paths which contain classes which are used in the project</li>
* <li>{@code -sp source path[:source paths...]} The optional source paths needed for JavaDoc analysis</li>
* <li>{@code -X} Debug enabled (prints error debugging information on Standard error out)</li>
* <li>{@code -n project name} The name of the project</li>
* <li>{@code -v project version} The version of the project</li>
* <li>{@code -d project domain} The domain of the project</li>
* <li>{@code -o output file} The location of the analysis output (will be printed to standard out if omitted)</li>
* <li>{@code -e encoding} The source file encoding</li>
* </ul>
* <p>
* Following available backend specific options (only have effect if the corresponding backend is selected):
* <ul>
* <li>{@code --swaggerSchemes scheme[,schemes]} The Swagger schemes: {@code http} (default), {@code https}, {@code ws}, {@code wss}")</li>
* <li>{@code --renderSwaggerTags} Enables rendering of Swagger tags (will not be rendered per default)</li>
* <li>{@code --swaggerTagsPathOffset path offset} The number at which path position the Swagger tags should be extracted ({@code 0} per default)</li>
* </ul>
*
* @param args The arguments
*/
public static void main(final String... args) {
if (args.length < 1) {
printUsageAndExit();
}
try {
extractArgs(args);
} catch (IllegalArgumentException e) {
System.err.println(e.getMessage() + '\n');
printUsageAndExit();
}
validateArgs();
final Backend backend = JAXRSAnalyzer.constructBackend(backendType);
backend.configure(attributes);
final JAXRSAnalyzer jaxrsAnalyzer = new JAXRSAnalyzer(projectClassPaths, projectSourcePaths, classPaths, name, version, backend, outputFileLocation);
jaxrsAnalyzer.analyze();
}
private static void extractArgs(String[] args) {
try {
for (int i = 0; i < args.length; i++) {
if (args[i].startsWith("-")) {
switch (args[i]) {
case "-b":
backendType = extractBackend(args[++i]);
break;
case "-cp":
extractClassPaths(args[++i]).forEach(classPaths::add);
break;
case "-sp":
extractClassPaths(args[++i]).forEach(projectSourcePaths::add);
break;
case "-X":
LogProvider.injectDebugLogger(System.err::println);
break;
case "-n":
name = args[++i];
break;
case "-v":
version = args[++i];
break;
case "-d":
attributes.put(SwaggerOptions.DOMAIN, args[++i]);
break;
case "-o":
outputFileLocation = Paths.get(args[++i]);
break;
case "-e":
System.setProperty("project.build.sourceEncoding", args[++i]);
break;
case "--swaggerSchemes":
attributes.put(SwaggerOptions.SWAGGER_SCHEMES, args[++i]);
break;
case "--renderSwaggerTags":
attributes.put(SwaggerOptions.RENDER_SWAGGER_TAGS, "true");
break;
case "--swaggerTagsPathOffset":
attributes.put(SwaggerOptions.SWAGGER_TAGS_PATH_OFFSET, args[++i]);
break;
case "-a":
addAttribute(args[++i]);
break;
default:
throw new IllegalArgumentException("Unknown option " + args[i]);
}
} else {
final Path path = Paths.get(args[i].replaceFirst("^~", System.getProperty("user.home")));
if (!path.toFile().exists()) {
System.err.println("Location " + path.toFile() + " doesn't exist\n");
printUsageAndExit();
}
projectClassPaths.add(path);
}
}
} catch (IndexOutOfBoundsException e) {
throw new IllegalArgumentException("Please provide valid number of arguments");
}
}
static Map<String, String> addAttribute(String attribute) {
int separatorIndex = attribute.indexOf('=');
if (separatorIndex < 0) {
attributes.put(attribute, "");
} else {
attributes.put(attribute.substring(0, separatorIndex).trim(), attribute.substring(separatorIndex + 1).trim());
}
return attributes;
}
private static String extractBackend(final String name) {
return name.toLowerCase();
}
private static List<Path> extractClassPaths(final String classPaths) {
final List<Path> paths = Stream.of(classPaths.split(File.pathSeparator))
.map(s -> s.replaceFirst("^~", System.getProperty("user.home")))
.map(Paths::get).collect(Collectors.toList());
paths.forEach(p -> {
if (!p.toFile().exists()) {
throw new IllegalArgumentException("Class path " + p.toFile() + " doesn't exist");
}
});
return paths;
}
private static void validateArgs() {
if (projectClassPaths.isEmpty()) {
System.err.println("Please provide at least one project path\n");
printUsageAndExit();
}
}
private static void printUsageAndExit() {
System.err.println("Usage: java -jar jaxrs-analyzer.jar [options] classPath [classPaths...]");
System.err.println("The classPath entries may be directories or jar-files containing the classes to be analyzed\n");
System.err.println("Following available options:\n");
System.err.println(" -b <backend> The backend to choose: swagger (default), plaintext, asciidoc");
System.err.println(" -cp <class path>[:class paths] Additional class paths (separated with colon) which contain classes used in the project (may be directories or jar-files)");
System.err.println(" -sp <source path>[:source paths] Optional source paths (separated with colon) needed for JavaDoc analysis (may be directories or jar-files)");
System.err.println(" -X Debug enabled (enabled error debugging information)");
System.err.println(" -n <project name> The name of the project");
System.err.println(" -v <project version> The version of the project");
System.err.println(" -d <project domain> The domain of the project");
System.err.println(" -o <output file> The location of the analysis output (will be printed to standard out if omitted)");
System.err.println(" -a <attribute name>=<attribute value> Set custom attributes for backends.");
System.err.println(" -e <encoding> The source file encoding");
System.err.println("\nFollowing available backend specific options (only have effect if the corresponding backend is selected):\n");
System.err.println(" --swaggerSchemes <scheme>[,schemes] The Swagger schemes: http (default), https, ws, wss");
System.err.println(" --renderSwaggerTags Enables rendering of Swagger tags (default tag will be used per default)");
System.err.println(" --swaggerTagsPathOffset <path offset> The number at which path position the Swagger tags will be extracted (0 will be used per default)");
System.err.println("\nExample: java -jar jaxrs-analyzer.jar -b swagger -n \"My Project\" -cp ~/libs/lib1.jar:~/libs/project/bin ~/project/target/classes");
System.exit(1);
}
}