All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.carma.swagger.doclet.parser.ParserHelper Maven / Gradle / Ivy

There is a newer version: 1.1.2
Show newest version
package com.carma.swagger.doclet.parser;

import static com.google.common.collect.Collections2.filter;
import static com.google.common.collect.Lists.transform;
import static java.util.Arrays.asList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import com.carma.swagger.doclet.DocletOptions;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.FieldDoc;
import com.sun.javadoc.MethodDoc;
import com.sun.javadoc.Parameter;
import com.sun.javadoc.ParameterizedType;
import com.sun.javadoc.ProgramElementDoc;
import com.sun.javadoc.SeeTag;
import com.sun.javadoc.Tag;
import com.sun.javadoc.Type;
import com.sun.javadoc.TypeVariable;

/**
 * The ParserHelper represents a helper class for the parsers
 * @version $Id$
 */
public class ParserHelper {

	private static final String JAX_RS_PATH_PARAM = "javax.ws.rs.PathParam";
	private static final String JAX_RS_QUERY_PARAM = "javax.ws.rs.QueryParam";
	private static final String JAX_RS_HEADER_PARAM = "javax.ws.rs.HeaderParam";
	private static final String JAX_RS_FORM_PARAM = "javax.ws.rs.FormParam";

	/**
	 * This is a set of the FQN of the various JAXRS parameter annotations
	 */
	public static final Set JAXRS_PARAM_ANNOTATIONS = new HashSet();
	static {
		// TODO support cookie and matrix params...
		JAXRS_PARAM_ANNOTATIONS.add(JAX_RS_PATH_PARAM);
		JAXRS_PARAM_ANNOTATIONS.add(JAX_RS_QUERY_PARAM);
		JAXRS_PARAM_ANNOTATIONS.add(JAX_RS_HEADER_PARAM);
		JAXRS_PARAM_ANNOTATIONS.add(JAX_RS_FORM_PARAM);
	}

	private static final String JAX_RS_ANNOTATION_PACKAGE = "javax.ws.rs";
	private static final String JAX_RS_PATH = "javax.ws.rs.Path";

	private static final String JAX_RS_CONSUMES = "javax.ws.rs.Consumes";
	private static final String JAX_RS_PRODUCES = "javax.ws.rs.Produces";
	private static final String JAX_RS_DEFAULT_VALUE = "javax.ws.rs.DefaultValue";

	@SuppressWarnings("serial")
	static final List PRIMITIVES = new ArrayList() {

		{
			add("byte");
			add("boolean");
			add("int");
			add("integer");
			add("long");
			add("float");
			add("double");
			add("short");
			add("char");
			add("string");
			add("date");
			add("number");
		}
	};

	/**
	 * This gets the allowable values from an enum class doc or null if the classdoc does not
	 * represent an enum
	 * @param typeClassDoc the class doc of the enum class to get the allowable values of
	 * @return The list of allowable values or null if this is not an enum
	 */
	public static List getAllowableValues(ClassDoc typeClassDoc) {
		// TODO use translator to support @XmlEnum values...
		List allowableValues = null;
		if (typeClassDoc != null && typeClassDoc.isEnum()) {
			allowableValues = transform(asList(typeClassDoc.enumConstants()), new Function() {

				public String apply(FieldDoc input) {
					if (input == null) {
						return null;
					}
					return input.name();
				}
			});
		}
		return allowableValues;
	}

	/**
	 * This gets whether a class doc has an ancestor class that can be processed, e.g.
	 * if its parent is java.lang.Object it returns false.
	 * @param classDoc The class doc
	 * @return True if the class doc class has a parent class that is not java.lang.Object.
	 */
	public static boolean hasAncestor(ClassDoc classDoc) {
		if (classDoc == null) {
			return false;
		}
		// ignore parent object class
		String qName = classDoc.qualifiedName();
		boolean isBaseObject = qName.equals("java.lang.Object");
		return !isBaseObject;
	}

	/**
	 * This gets the default value of the given parameter
	 * @param param The parameter
	 * @param options The doclet options
	 * @return The default value or null if it has no default
	 */
	public static String getDefaultValue(Parameter param, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(param, options);
		String value = p.getAnnotationValue(JAX_RS_DEFAULT_VALUE, "value");
		return value;
	}

	/**
	 * This parses the path from the annotations of a method or class
	 * @param doc The method or class
	 * @param options The doclet options
	 * @return The path or null if no path related annotations were present
	 */
	public static String parsePath(com.sun.javadoc.ProgramElementDoc doc, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(doc, options);
		String path = p.getAnnotationValue(JAX_RS_PATH, "value");
		if (path != null) {
			if (path.endsWith("/")) {
				path = path.substring(0, path.length() - 1);
			}
			if (!path.isEmpty() && !path.startsWith("/")) {
				path = "/" + path;
			}

			return path;
		}
		return null;
	}

	/**
	 * This verifies that the given numeric values are valid for the given data type and format and returns the comparison.
	 * If not valid it raises an exception. It ignores null/empty values.
	 * @param context Additional description for contextualizing the error message
	 * @param type The data type as per json schema
	 * @param format The data format
	 * @param value1 The first value to check
	 * @param value2 The 2nd value to check
	 * @throws IllegalStateException if the value is invalid.
	 * @return null if either value is null/empty, otherwise -1,0,1 as per standard java compare behaviour.
	 */
	public static Integer compareNumericValues(String context, String type, String format, String value1, String value2) {
		if (value1 == null || value1.trim().isEmpty() || value2 == null || value2.trim().isEmpty()) {
			return null;
		}
		Number val1 = verifyNumericValue(context, type, format, value1);
		Number val2 = verifyNumericValue(context, type, format, value1);
		return Double.valueOf(val1.doubleValue()).compareTo(val2.doubleValue());
	}

	/**
	 * This verifies that the given value is valid for the given data type and format for use as a numeric value
	 * which means it must be integer or number type.
	 * If not valid it raises an exception. It ignores null/empty values.
	 * @param context Additional description for contextualizing the error message
	 * @param type The data type as per json schema
	 * @param format The data format
	 * @param value The value to check
	 * @throws IllegalStateException if the value is invalid.
	 * @return The value as a number suitable for the given type and format
	 */
	public static Number verifyNumericValue(String context, String type, String format, String value) {
		if (value == null || value.trim().isEmpty()) {
			return null;
		}
		try {
			if (type.equals("integer")) {
				if (format.equals("int32")) {
					return Integer.parseInt(value);
				} else {
					return Long.parseLong(value);
				}
			} else if (type.equals("number")) {
				if (format.equals("double")) {
					return Double.parseDouble(value);
				} else {
					return Float.parseFloat(value);
				}
			} else if (format != null && format.equals("byte")) {
				return Byte.parseByte(value);
			}
		} catch (NumberFormatException nfe) {
			throw new IllegalStateException("The value was not valid for the type: " + type + " and format: " + format + context, nfe);
		}
		throw new IllegalStateException("Min/Max values are not allowed for the type: " + type + " and format: " + format + context);
	}

	/**
	 * This verifies that the given value is valid for the given data type and format.
	 * If not valid it raises an exception. It ignores null/empty values.
	 * @param context Additional description for contextualizing the error message
	 * @param type The data type as per json schema
	 * @param format The data format
	 * @param value The value to check
	 * @throws IllegalStateException if the value is invalid.
	 */
	public static void verifyValue(String context, String type, String format, String value) {
		if (value == null || value.trim().isEmpty()) {
			return;
		}
		try {
			if (type.equals("integer")) {
				if (format.equals("int32")) {
					Integer.parseInt(value);
				} else {
					Long.parseLong(value);
				}
			} else if (type.equals("number")) {
				if (format.equals("double")) {
					Double.parseDouble(value);
				} else {
					Float.parseFloat(value);
				}
			} else if (format != null && format.equals("byte")) {
				Byte.parseByte(value);
			} else if (type.equals("boolean")) {
				if (!value.equalsIgnoreCase("true") && !value.equals("false")) {
					throw new IllegalStateException("The value was not valid for the type: " + type + " and format: " + format + context);
				}
			}
			// TODO support date and date time
		} catch (NumberFormatException nfe) {
			throw new IllegalStateException("The value was not valid for the type: " + type + " and format: " + format + context, nfe);
		}
	}

	/**
	 * This gets the qualified type name of a javadoc type,
	 * It adds [] onto any array types
	 * @param type The type
	 * @return The qualified type name
	 */
	public static String getQualifiedTypeName(Type type) {
		String qName = type.qualifiedTypeName();
		// handle arrays
		String dimension = type.dimension();
		if (dimension != null && "[]".equals(dimension)) {
			qName = qName + "[]";
		}
		return qName;
	}

	/**
	 * Determines the String representation of the given FQN.
	 * This includes the type as the first item and the format as the 2nd.
	 * Common name Swagger spec 1.2
	 * integer integer, int32
	 * long integer, int64
	 * float number, float
	 * double number, double
	 * string string
	 * byte string, byte
	 * boolean boolean
	 * date string, date
	 * dateTime string, date-time
	 * @param javaType The java type to get the swagger type and format of
	 * @param options The doclet options
	 * @return An array with the type as the first item and the format as the 2nd.
	 */
	public static String[] typeOf(String javaType, DocletOptions options) {

		if (javaType.toLowerCase().equals("byte[]")) {
			return new String[] { "ByteArray", null };
		} else if (javaType.toLowerCase().equals("byte") || javaType.equalsIgnoreCase("java.lang.Byte")) {
			return new String[] { "string", "byte" };
		} else if (javaType.toLowerCase().equals("int") || javaType.toLowerCase().equals("integer") || javaType.equalsIgnoreCase("java.lang.Integer")) {
			return new String[] { "integer", "int32" };
		} else if (javaType.toLowerCase().equals("short") || javaType.equalsIgnoreCase("java.lang.Short")) {
			return new String[] { "integer", "int32" };
		} else if (javaType.toLowerCase().equals("long") || javaType.equalsIgnoreCase("java.lang.Long")) {
			return new String[] { "integer", "int64" };
		} else if (javaType.toLowerCase().equals("float") || javaType.equalsIgnoreCase("java.lang.Float")) {
			return new String[] { "number", "float" };
		} else if (javaType.toLowerCase().equals("double") || javaType.equalsIgnoreCase("java.lang.Double")) {
			return new String[] { "number", "double" };
		} else if (javaType.toLowerCase().equals("string") || javaType.equalsIgnoreCase("java.lang.String")) {
			return new String[] { "string", null };
		} else if (javaType.toLowerCase().equals("char") || javaType.equalsIgnoreCase("java.lang.Character")) {
			return new String[] { "string", null };
		} else if (javaType.toLowerCase().equals("boolean") || javaType.equalsIgnoreCase("java.lang.Boolean")) {
			return new String[] { "boolean", null };
		} else if (javaType.toLowerCase().equals("date") || javaType.equalsIgnoreCase("java.util.Date")) {
			return new String[] { "string", "date-time" };
		} else if (isCollection(javaType)
				&& (javaType.toLowerCase().endsWith("list") || javaType.toLowerCase().endsWith("array") || javaType.toLowerCase().endsWith("collection"))) {
			return new String[] { "array", null };
		} else if (isSet(javaType)) {
			return new String[] { "array", null };
		} else if (javaType.equalsIgnoreCase("java.io.File")) {
			// special handling of files, the datatype File is reserved for multipart
			return new String[] { "JavaFile", null };
		} else {

			// see if its a special string type
			for (String prefix : options.getStringTypePrefixes()) {
				if (javaType.startsWith(prefix)) {
					return new String[] { "string", null };
				}
			}

			// must be a complex type, return class name
			int i = javaType.lastIndexOf(".");
			if (i >= 0) {
				return new String[] { javaType.substring(i + 1), null };
			} else {
				return new String[] { javaType, null };
			}
		}

	}

	/**
	 * Determines the String representation of the object Type.
	 * This includes the type as the first item and the format as the 2nd.
	 * Common name Swagger spec 1.2
	 * integer integer, int32
	 * long integer, int64
	 * float number, float
	 * double number, double
	 * string string
	 * byte string, byte
	 * boolean boolean
	 * date string, date
	 * dateTime string, date-time
	 * @param type The java type to get the swagger type and format of
	 * @param options The doclet options
	 * @return An array with the type as the first item and the format as the 2nd.
	 */
	public static String[] typeOf(Type type, DocletOptions options) {

		String javaType = getQualifiedTypeName(type);

		return typeOf(javaType, options);
	}

	/**
	 * This gets the type that a container holds
	 * @param type The raw type like Collection
	 * @param varsToTypes A map of variables to types for parameterized types, optional if null parameterized types
	 *            will not be handled
	 * @return The container type or null if not a collection
	 */
	public static Type getContainerType(Type type, Map varsToTypes) {
		Type result = null;
		ParameterizedType pt = type.asParameterizedType();
		if (pt != null && ParserHelper.isCollection(type.qualifiedTypeName())) {
			Type[] typeArgs = pt.typeArguments();
			if (typeArgs != null && typeArgs.length > 0) {
				result = typeArgs[0];
			}
		}
		// if its a ref to a param type replace with the type impl
		if (result != null) {
			Type paramType = getVarType(result.asTypeVariable(), varsToTypes);
			if (paramType != null) {
				return paramType;
			}
		}
		return result;
	}

	/**
	 * This finds a variable type and returns its impl from the given map
	 * @param var The variable type to find
	 * @param varsToTypes The map of variables to types
	 * @return The result.
	 */
	public static Type getVarType(TypeVariable var, Map varsToTypes) {
		Type res = null;
		if (var != null && varsToTypes != null) {
			Type type = varsToTypes.get(var.qualifiedTypeName());
			while (type != null) {
				res = type;
				type = varsToTypes.get(type.qualifiedTypeName());
			}
		}
		return res;
	}

	/**
	 * This gets whether the given type is a Set
	 * @param javaType The java type
	 * @return True if this is a Set
	 */
	public static boolean isSet(String javaType) {
		try {
			return java.util.Set.class.isAssignableFrom(Class.forName(javaType));
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	/**
	 * This gets whether the given type is a Collection
	 * @param javaType The java type
	 * @return True if this is a collection
	 */
	public static boolean isCollection(String javaType) {
		try {
			return java.util.Collection.class.isAssignableFrom(Class.forName(javaType));
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	/**
	 * This gets whether the given type is a Map
	 * @param javaType The java type
	 * @return True if this is a map
	 */
	public static boolean isMap(String javaType) {
		try {
			return java.util.Map.class.isAssignableFrom(Class.forName(javaType));
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	/**
	 * This gets whether the given parameter is a File data type
	 * @param parameter The parameter
	 * @param options The doclet options
	 * @return True if the parameter is a File data type
	 */
	public static boolean isFileParameterDataType(Parameter parameter, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(parameter, options);
		for (String fileAnnotation : options.getFileParameterAnnotations()) {
			if (p.isAnnotatedBy(fileAnnotation)) {
				return true;
			}
		}
		String qName = ParserHelper.getQualifiedTypeName(parameter.type());
		for (String fileType : options.getFileParameterTypes()) {
			if (qName.equals(fileType)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets (for composite param fields) whether the given parameter is a File data type
	 * @param paramMember The field or method that is the parameter
	 * @param type The type of the parameter
	 * @param options The doclet options
	 * @return True if the parameter is a File data type
	 */
	private static boolean isFileParameterDataType(ProgramElementDoc paramMember, Type type, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(paramMember, options);
		for (String fileAnnotation : options.getFileParameterAnnotations()) {
			if (p.isAnnotatedBy(fileAnnotation)) {
				return true;
			}
		}
		String qName = ParserHelper.getQualifiedTypeName(type);
		for (String fileType : options.getFileParameterTypes()) {
			if (qName.equals(fileType)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * Determines the string representation of the parameter type for composite types.
	 * @param returnDefault Whether to return a default value if there is no specific jaxrs param annotation
	 * @param multipart Whether the method the parameter is for consumes multipart
	 * @param paramMember The field or method that is the parameter
	 * @param type The type of the parameter
	 * @param options The doclet options
	 * @return The type of parameter, one of path, header, query, form or body.
	 */
	public static String paramTypeOf(boolean returnDefault, boolean multipart, ProgramElementDoc paramMember, Type type, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(paramMember, options);
		if (p.isAnnotatedBy(JAX_RS_PATH_PARAM)) {
			return "path";
		} else if (p.isAnnotatedBy(JAX_RS_HEADER_PARAM)) {
			return "header";
		} else if (p.isAnnotatedBy(JAX_RS_QUERY_PARAM)) {
			return "query";
		} else if (p.isAnnotatedBy(JAX_RS_FORM_PARAM)) {
			return "form";
		}

		String qName = getQualifiedTypeName(type);

		// bean param and other composites
		for (String compositeAnnotation : options.getCompositeParamAnnotations()) {
			if (p.isAnnotatedBy(compositeAnnotation)) {
				return "composite";
			}
		}
		for (String compositeType : options.getCompositeParamTypes()) {
			if (qName.equals(compositeType)) {
				return "composite";
			}
		}

		// look for form parameter types
		for (String formAnnotation : options.getFormParameterAnnotations()) {
			if (p.isAnnotatedBy(formAnnotation)) {
				return "form";
			}
		}
		for (String formType : options.getFormParameterTypes()) {
			if (qName.equals(formType)) {
				return "form";
			}
		}

		// look for File data types, for multipart these are always form parameter types
		// as per the swagger 1.2 spec
		if (multipart && isFileParameterDataType(paramMember, type, options)) {
			return "form";
		}

		if (!returnDefault) {
			return null;
		}

		// otherwise default to body
		return "body";
	}

	/**
	 * Determines the string representation of the parameter type.
	 * @param multipart Whether the method the parameter is for consumes multipart
	 * @param parameter The parameter to get the type of
	 * @param options The doclet options
	 * @return The type of parameter, one of path, header, query, form or body.
	 */
	public static String paramTypeOf(boolean multipart, Parameter parameter, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(parameter, options);
		if (p.isAnnotatedBy(JAX_RS_PATH_PARAM)) {
			return "path";
		} else if (p.isAnnotatedBy(JAX_RS_HEADER_PARAM)) {
			return "header";
		} else if (p.isAnnotatedBy(JAX_RS_QUERY_PARAM)) {
			return "query";
		} else if (p.isAnnotatedBy(JAX_RS_FORM_PARAM)) {
			return "form";
		}

		String qName = getQualifiedTypeName(parameter.type());

		// bean param and other composites
		for (String compositeAnnotation : options.getCompositeParamAnnotations()) {
			if (p.isAnnotatedBy(compositeAnnotation)) {
				return "composite";
			}
		}
		for (String compositeType : options.getCompositeParamTypes()) {
			if (qName.equals(compositeType)) {
				return "composite";
			}
		}

		// look for form parameter types
		for (String formAnnotation : options.getFormParameterAnnotations()) {
			if (p.isAnnotatedBy(formAnnotation)) {
				return "form";
			}
		}
		for (String formType : options.getFormParameterTypes()) {
			if (qName.equals(formType)) {
				return "form";
			}
		}

		// look for File data types, for multipart these are always form parameter types
		// as per the swagger 1.2 spec
		if (multipart && isFileParameterDataType(parameter, options)) {
			return "form";
		}

		// otherwise default to body
		return "body";
	}

	/**
	 * Determines the string representation of the parameter name.
	 * @param parameter The parameter to get the name of that is used for the api
	 * @param overrideParamNames A map of rawname to override names for parameters
	 * @param paramNameAnnotations List of FQN of annotations that can be used for the parameter name
	 * @param options The doclet options
	 * @return the name of the parameter used by http requests
	 */
	public static String paramNameOf(Parameter parameter, Map overrideParamNames, List paramNameAnnotations, DocletOptions options) {

		String name = null;
		String rawName = parameter.name();

		// if there is an override name use that ahead of any annotation
		if (overrideParamNames != null && overrideParamNames.containsKey(rawName)) {
			name = overrideParamNames.get(rawName);
		}
		// look for any of the configured annotations that can determine the parameter name
		if (name == null) {
			AnnotationParser p = new AnnotationParser(parameter, options);
			for (String paramNameAnnotation : paramNameAnnotations) {
				name = p.getAnnotationValue(paramNameAnnotation, "value");
				if (name != null) {
					break;
				}
			}
		}
		// otherwise use the raw parameter name as defined on the method signature
		if (name == null) {
			name = rawName;
		}

		return name;
	}

	/**
	 * This gets the json views for the given method/field
	 * @param doc The method/field to get the json views of
	 * @param options The doclet options
	 * @return The json views for the given method/field or null if there were none
	 */
	public static ClassDoc[] getJsonViews(com.sun.javadoc.ProgramElementDoc doc, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(doc, options);
		ClassDoc[] viewClasses = p.getAnnotationClassDocValues("com.fasterxml.jackson.annotation.JsonView", "value");
		if (viewClasses == null) {
			viewClasses = p.getAnnotationClassDocValues("org.codehaus.jackson.map.annotate.JsonView", "value");
		}
		return viewClasses;
	}

	/**
	 * This gets whether the given method/field has a json view on it
	 * @param doc The method/field to check
	 * @param options The doclet options
	 * @return True if the given method/field has a json view
	 */
	public static boolean hasJsonViews(com.sun.javadoc.ProgramElementDoc doc, DocletOptions options) {
		return getJsonViews(doc, options) != null;
	}

	/**
	 * This checks if an item view e.g optional json view that can be on a getter/field match any of the
	 * given operation views, that is it can be the same or extend/implement one of the operation views.
	 * @param operationViews The operation views that indicate which views apply to the operation.
	 * @param itemsViews The views that are on the getter/field
	 * @return True if the field/getter is part of the view
	 */
	public static boolean isItemPartOfView(ClassDoc[] operationViews, ClassDoc[] itemsViews) {
		if (operationViews != null && itemsViews != null) {
			// check that one of the operation views is a subclass of an item view
			for (ClassDoc operationView : operationViews) {
				if (isAssignableFrom(itemsViews, operationView)) {
					return true;
				}
			}
			return false;
		}
		return true;
	}

	/**
	 * This checks if the given clazz is the same as or implments or is a subclass/sub interface of
	 * any of the given classes
	 * @param superClasses the classes to check if they are super classes/super interfaces of the given class
	 * @param clazz The class to check if it extends/implements any of the given classes
	 * @return True if the given class extends/implements any of the given classes/interfaces
	 */
	public static boolean isAssignableFrom(ClassDoc[] superClasses, ClassDoc clazz) {
		if (superClasses != null) {
			for (ClassDoc superClazz : superClasses) {
				if (isAssignableFrom(superClazz, clazz)) {
					return true;
				}
			}
		}
		return false;
	}

	private static boolean isAssignableFrom(ClassDoc superClass, ClassDoc clazz) {
		if (clazz.subclassOf(superClass)) {
			return true;
		}
		if (superClass.isInterface()) {
			// if one of the classes interfaces is the super class interface
			// or a subclass interface of the super class then its assignable
			ClassDoc[] subInterfaces = clazz.interfaces();
			if (subInterfaces != null) {
				for (ClassDoc subInterface : subInterfaces) {
					if (subInterface.subclassOf(superClass)) {
						return true;
					}
				}
			}
		}
		return false;
	}

	/**
	 * This gets the list of consumes mime types from the given method
	 * @param methodDoc The method javadoc
	 * @param options The doclet options
	 * @return The list or null if none were found
	 */
	public static List getConsumes(MethodDoc methodDoc, DocletOptions options) {
		return listValues(methodDoc, JAX_RS_CONSUMES, "value", options);
	}

	/**
	 * This gets the list of produces mime types from the given method
	 * @param methodDoc The method javadoc
	 * @param options The doclet options
	 * @return The list or null if none were found
	 */
	public static List getProduces(MethodDoc methodDoc, DocletOptions options) {
		return listValues(methodDoc, JAX_RS_PRODUCES, "value", options);
	}

	/**
	 * This gets a list of values from an annotation that uses a string array value
	 * @param doc The method/field doc
	 * @param qualifiedAnnotationType The FQN of the annotation
	 * @param annotationValueName The name of the value field of the annotation to use
	 * @param options The doclet options
	 * @return A list of values or null if none were found
	 */
	public static List listValues(com.sun.javadoc.ProgramElementDoc doc, String qualifiedAnnotationType, String annotationValueName,
			DocletOptions options) {
		AnnotationParser p = new AnnotationParser(doc, options);
		String[] vals = p.getAnnotationValues(qualifiedAnnotationType, annotationValueName);
		if (vals != null && vals.length > 0) {
			List res = new ArrayList(vals.length);
			for (String val : vals) {
				if (val != null && val.trim().length() > 0) {
					res.add(val.trim());
				}
			}
			if (!res.isEmpty()) {
				return res;
			}
		}
		return null;
	}

	/**
	 * This gets whether the given type is primitive
	 * @param type The type to check
	 * @param options The doclet options
	 * @return True if the given type is primitive
	 */
	public static boolean isPrimitive(String type, DocletOptions options) {
		if (type == null) {
			return false;
		}
		return PRIMITIVES.contains(typeOf(type, options)[0]);
	}

	/**
	 * This gets whether the given type is primitive
	 * @param type The type to check
	 * @param options The doclet options
	 * @return True if the given type is primitive
	 */
	public static boolean isPrimitive(Type type, DocletOptions options) {
		if (type == null) {
			return false;
		}
		return PRIMITIVES.contains(typeOf(type, options)[0]);
	}

	/**
	 * This gets whether the given item has any of the given tags
	 * @param item The javadoc item
	 * @param matchTags The names of the tags to look for
	 * @return True if the item has any of the given tags
	 */
	public static boolean hasTag(com.sun.javadoc.ProgramElementDoc item, Collection matchTags) {
		if (matchTags != null) {
			for (String matchTag : matchTags) {
				Tag[] tags = item.tags(matchTag);
				if (tags != null && tags.length > 0) {
					return true;
				}
			}
		}
		return false;
	}

	/**
	 * This gets the parameter names for the given method
	 * @param method The method
	 * @return the names of the method parameters or an empty set if the method had none
	 */
	public static Set getParamNames(com.sun.javadoc.MethodDoc method) {
		Set params = new HashSet();
		for (Parameter parameter : method.parameters()) {
			params.add(parameter.name());
		}
		return params;
	}

	/**
	 * This gets a map of parameter name to value from a javadoc tag on a method.
	 * This validates that the names of the parameters in each NVP is an actual method parameter
	 * @param method The method
	 * @param params The pre-read params of the method, if null they will be read from the given method
	 * @param matchTags The names of the javadoc tags to look for
	 * @param options The doclet options
	 * @return a map of parameter name to value from a javadoc tag on a method or an empty map if none were found
	 */
	public static Map getMethodParamNameValuePairs(com.sun.javadoc.MethodDoc method, Set params, Collection matchTags,
			DocletOptions options) {
		String value = getTagValue(method, matchTags, options);
		if (value != null) {
			String[] parts = value.split("\\s+");
			if (parts != null && parts.length > 0) {
				if (parts.length % 2 != 0) {
					throw new IllegalStateException(
							"Invalid javadoc parameter on method "
									+ method.name()
									+ " for tags: "
									+ matchTags
									+ ". The value had "
									+ parts.length
									+ " whitespace seperated parts when it was expected to have name value pairs for each parameter, e.g. the number of parts should have been even.");
				}
				if (params == null) {
					params = getParamNames(method);
				}
				Map res = new HashMap(parts.length / 2);
				for (int i = 0; i < parts.length; i += 2) {
					String name = parts[i];
					if (!params.contains(name)) {
						throw new IllegalStateException("Invalid javadoc parameter on method " + method.name() + " for tags: " + matchTags + ". The parameter "
								+ name + " is not the name of one of the method parameters.");
					}
					String val = parts[i + 1];
					res.put(name, val);
				}
				return res;
			}
		}
		return Collections.emptyMap();
	}

	/**
	 * This gets a list of parameter names from a method javadoc tag where the value of the tag is in the form
	 * paramName1,paramName2 ... paramNameN
	 * @param method The method
	 * @param params The pre-read params of the method, if null they will be read from the given method
	 * @param matchTags The names of the javadoc tags to look for
	 * @param options The doclet options
	 * @return The list of parameter names or an empty list if there were none
	 */
	public static List getCsvParams(com.sun.javadoc.MethodDoc method, Set params, Collection matchTags, DocletOptions options) {
		if (params == null) {
			params = getParamNames(method);
		}
		List tagParams = getTagCsvValues(method, matchTags, options);
		// check each param is an actual param of the method
		if (tagParams != null) {
			for (String param : tagParams) {
				if (!params.contains(param)) {
					throw new IllegalStateException("Invalid javadoc parameter on method " + method.name() + " for tags: " + matchTags + ". The parameter "
							+ param + " is not the name of one of the method parameters.");
				}
			}
			return tagParams;
		}
		return Collections.emptyList();
	}

	/**
	 * This gets a csv javadoc tag value as a list of the values in the csv for the first matched tag.
	 * e.g. @myTag 1,2,3 would return a list of 1,2,3 assuming matchTags contained myTag
	 * @param item The javadoc item
	 * @param matchTags The tags to match
	 * @param options The doclet options
	 * @return The csv values of the first matching tags value or an empty list if there were none.
	 */
	public static List getTagCsvValues(com.sun.javadoc.ProgramElementDoc item, Collection matchTags, DocletOptions options) {
		String value = getTagValue(item, matchTags, options);
		if (value != null) {
			String[] vals = value.split(",");
			if (vals != null && vals.length > 0) {
				List res = new ArrayList();
				for (String val : vals) {
					if (val != null && val.trim().length() > 0) {
						res.add(options.replaceVars(val.trim()));
					}
				}
				return res.isEmpty() ? null : res;
			}
		}
		return Collections.emptyList();
	}

	/**
	 * This gets the value of the first tag found from the given collection of tag names
	 * @param item The item to get the tag value of
	 * @param matchTags The collection of tag names of the tag to get a value of
	 * @param options The doclet options
	 * @return The value of the first tag found with the name in the given collection or null if either the tag
	 *         was not present or had no value
	 */
	public static String getTagValue(com.sun.javadoc.ProgramElementDoc item, Collection matchTags, DocletOptions options) {
		String customValue = null;
		if (matchTags != null) {
			for (String matchTag : matchTags) {
				Tag[] tags = item.tags(matchTag);
				if (tags != null && tags.length > 0) {
					customValue = tags[0].text().trim();
					if (customValue.length() == 0) {
						customValue = null;
					}
					break;
				}
			}
		}
		return options.replaceVars(customValue);
	}

	private static final Set DEPRECATED_TAGS = new HashSet();
	private static final Set DEPRECATED_ANNOTATIONS = new HashSet();
	static {
		DEPRECATED_TAGS.add("deprecated");
		DEPRECATED_TAGS.add("Deprecated");
		DEPRECATED_ANNOTATIONS.add("java.lang.Deprecated");
	}

	/**
	 * This gets whether the given annotations have one of the deprecated ones
	 * @param annotations The annotations to check
	 * @return True if the annotations array contains a deprecated one
	 */
	public static boolean hasDeprecated(AnnotationDesc[] annotations) {
		if (annotations != null && annotations.length > 0) {
			List allAnnotations = Arrays.asList(annotations);
			Collection excluded = filter(allAnnotations, new ParserHelper.ExcludedAnnotations(DEPRECATED_ANNOTATIONS));
			if (!excluded.isEmpty()) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets whether the given item is marked as deprecated either via a javadoc tag
	 * or an annotation
	 * @param item The item to check
	 * @return True if the item is flagged as deprecated
	 */
	public static boolean isDeprecated(com.sun.javadoc.ProgramElementDoc item) {
		if (hasTag(item, DEPRECATED_TAGS)) {
			return true;
		}
		return hasDeprecated(item.annotations());
	}

	/**
	 * This gets values of any of the javadoc tags that are in the given collection
	 * @param item The javadoc item to get the tags of
	 * @param matchTags The names of the tags to get
	 * @param options The doclet options
	 * @return A list of tag values or null if none were found
	 */
	public static List getTagValues(com.sun.javadoc.ProgramElementDoc item, Collection matchTags, DocletOptions options) {
		List res = null;
		if (matchTags != null) {
			for (String matchTag : matchTags) {
				Tag[] tags = item.tags(matchTag);
				if (tags != null && tags.length > 0) {
					for (Tag tag : tags) {
						if (res == null) {
							res = new ArrayList();
						}
						String customValue = tag.text().trim();
						if (customValue.length() > 0) {
							res.add(options.replaceVars(customValue));
						}
					}
				}
			}
		}
		return res == null || res.isEmpty() ? null : res;
	}

	/**
	 * This builds a map of FQN to type for all see annotations
	 * on the given items javadoc
	 * @param item The item to get the see types of
	 * @return A map of see types or an empty map if there were no see tags.
	 */
	public static Map readSeeTypes(com.sun.javadoc.ProgramElementDoc item) {
		Map types = new HashMap();
		SeeTag[] seeTags = item.seeTags();
		if (seeTags != null) {
			for (SeeTag seeTag : seeTags) {
				Type type = seeTag.referencedClass();
				types.put(seeTag.referencedClassName(), type);
			}
		}
		return types;
	}

	/**
	 * This finds a model class by the given name
	 * @param classes The model classes
	 * @param qualifiedClassName The FQN of the class
	 * @return {@link ClassDoc} found among all classes processed by the doclet based on a given qualifiedClassName; null if not found
	 */
	public static ClassDoc findModel(Collection classes, String qualifiedClassName) {
		if (classes != null && qualifiedClassName != null) {
			for (ClassDoc cls : classes) {
				if (qualifiedClassName.equals(cls.qualifiedName())) {
					return cls;
				}
			}
		}
		return null;
	}

	/**
	 * The ExcludedAnnotations represents a filter that can be used for filtering
	 * only if the specified annotation classes are present
	 */
	public static class ExcludedAnnotations implements Predicate {

		private final Collection annotationClasses;

		/**
		 * This creates a ExcludedAnnotations
		 * @param annotationClasses
		 */
		public ExcludedAnnotations(Collection annotationClasses) {
			this.annotationClasses = annotationClasses;
		}

		public boolean apply(AnnotationDesc annotationDesc) {
			String annotationClass = annotationDesc.annotationType().qualifiedTypeName();
			return this.annotationClasses.contains(annotationClass);
		}
	}

	/**
	 * The JaxRsAnnotations represents a predicate that can be used for filtering
	 * only if a jaxrs annotation is present
	 */
	public static class JaxRsAnnotations implements Predicate {

		public boolean apply(AnnotationDesc annotationDesc) {
			String annotationClass = annotationDesc.annotationType().qualifiedTypeName();
			return annotationClass.startsWith(JAX_RS_ANNOTATION_PACKAGE);
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy