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

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

package com.carma.swagger.doclet.parser;

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

import java.util.ArrayList;
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.carma.swagger.doclet.model.HttpMethod;
import com.google.common.base.Function;
import com.sun.javadoc.AnnotationDesc;
import com.sun.javadoc.ClassDoc;
import com.sun.javadoc.ExecutableMemberDoc;
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";

	private 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);
	}

	/**
	 * This is a set of the FQN of the various JAXRS parameter annotations
	 */
	public static final Set JAXRS_PARAM_ANNOTATIONS = Collections.unmodifiableSet(_JAXRS_PARAM_ANNOTATIONS);

	private static final String JAX_RS_ANNOTATION_PACKAGE = "javax.ws.rs";
	private static final Set JAX_RS_PREFIXES = new HashSet();
	static {
		JAX_RS_PREFIXES.add(JAX_RS_ANNOTATION_PACKAGE);
	}

	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 an annotation value from the given class, it supports looking at super classes
	 * @param classDoc The class to look for the annotation
	 * @param options The doclet options
	 * @param qualifiedAnnotationType The FQN of the annotation to look for
	 * @param keys The keys for the annotation values
	 * @return The value of the annotation or null if none was found
	 */
	public static String getInheritableClassLevelAnnotationValue(ClassDoc classDoc, DocletOptions options, String qualifiedAnnotationType, String... keys) {
		ClassDoc currentClassDoc = classDoc;
		while (currentClassDoc != null) {

			AnnotationParser p = new AnnotationParser(currentClassDoc, options);
			String value = p.getAnnotationValue(qualifiedAnnotationType, keys);

			if (value != null) {
				return value;
			}

			currentClassDoc = currentClassDoc.superclass();
			// ignore parent object class
			if (!ParserHelper.hasAncestor(currentClassDoc)) {
				break;
			}
		}
		return null;
	}

	/**
	 * 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;
	}

	/**
	 * Resolves the @Path for the ClassDoc supporting inheritance
	 * @param classDoc The class to be processed
	 * @param options Doclet options
	 * @return The resolved path
	 */
	public static String resolveClassPath(ClassDoc classDoc, DocletOptions options) {
		String path = ParserHelper.getInheritableClassLevelAnnotationValue(classDoc, options, JAX_RS_PATH, "value");
		return normalisePath(path);
	}

	/**
	 * Resolves tha @Path for the MethodDoc respecting the overriden methods
	 * @param methodDoc The method to be processed
	 * @param options Doclet options
	 * @return The resolved path
	 */
	public static String resolveMethodPath(MethodDoc methodDoc, DocletOptions options) {
		String path = null;
		while (path == null && methodDoc != null) {
			AnnotationParser p = new AnnotationParser(methodDoc, options);
			path = p.getAnnotationValue(JAX_RS_PATH, "value");
			methodDoc = methodDoc.overriddenMethod();
		}
		return normalisePath(path);
	}

	/**
	 * This normalises a path to ensure it starts with / and does not end with /
	 * @param path The path to normalize
	 * @return The path or an empty string if given no path
	 */
	private static String normalisePath(String path) {
		if (path != null) {
			path = path.trim();
			if (path.endsWith("/")) {
				path = path.substring(0, path.length() - 1);
			}
			if (!path.isEmpty() && !path.startsWith("/")) {
				path = "/" + path;
			}

			return path;
		}
		return "";
	}

	/**
	 * 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.compare(val1.doubleValue(), 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.equalsIgnoreCase("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) {
		String[] simpleType = primitiveTypeOf(javaType, options);

		if (simpleType != null) {
			return simpleType;
		} else if (isCollection(javaType)) {
			return new String[] { "array", null };
		} else if (isSet(javaType)) {
			return new String[] { "array", null };
		} else if (isArray(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 {

			// support inner classes, for this we use case sensitivity
			// e.g. com.my.Foo.Bar should map to Foo-Bar
			int startPos = -1;
			for (int i = 0; i < javaType.length(); i++) {
				char c = javaType.charAt(i);
				if (Character.isUpperCase(c)) {
					startPos = i;
					break;
				}
			}
			if (startPos == -1) {
				startPos = javaType.lastIndexOf(".") + 1;
				if (startPos > javaType.length() - 1) {
					startPos = -1;
				}
			}

			if (startPos >= 0) {
				String typeName = javaType.substring(startPos).replace(".", "-");
				return new String[] { typeName, 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 swagger type and format for a javatype but only looks at primitives
	 * @param javaType The java type
	 * @param options the doclet options
	 * @return The swagger type and format or null if the java type is not a primitive
	 */
	public static String[] primitiveTypeOf(String javaType, DocletOptions options) {
		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") || javaType.equalsIgnoreCase("java.math.BigInteger")) {
			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")
				|| javaType.equalsIgnoreCase("java.math.BigDecimal")) {
			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.equalsIgnoreCase("java.time.LocalDate")) {
			return new String[] { "string", "date" };

		} else if (javaType.equalsIgnoreCase("java.time.Year")) {
			return new String[] { "integer", "int32" };

		} else if (javaType.toLowerCase().equals("date") || javaType.equalsIgnoreCase("java.util.Date") || javaType.equalsIgnoreCase("java.time.LocalDateTime")
				|| javaType.equalsIgnoreCase("java.time.OffsetDateTime") || javaType.equalsIgnoreCase("java.time.ZonedDateTime")
				|| javaType.equalsIgnoreCase("java.time.Instant")) {
			return new String[] { "string", "date-time" };

		} else if (javaType.equalsIgnoreCase("java.time.OffsetTime") || javaType.equalsIgnoreCase("java.time.Duration")
				|| javaType.equalsIgnoreCase("java.time.MonthDay") || javaType.equalsIgnoreCase("java.time.Period")
				|| javaType.equalsIgnoreCase("java.time.Month") || javaType.equalsIgnoreCase("java.time.DayOfWeek")
				|| javaType.equalsIgnoreCase("java.time.YearMonth") || javaType.equalsIgnoreCase("java.time.ZoneId")
				|| javaType.equalsIgnoreCase("java.time.ZoneOffset")) {
			return new String[] { "string", null };

		} else if (javaType.toLowerCase().equals("uuid") || javaType.equalsIgnoreCase("java.util.UUID")) {
			return new String[] { "string", "uuid" };
		}

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

		return null;
	}

	/**
	 * This gets the swagger type and format for a javatype but only looks at primitives
	 * @param type The java type
	 * @param options the doclet options
	 * @return The swagger type and format or null if the java type is not a primitive
	 */
	public static String[] primitiveTypeOf(Type type, DocletOptions options) {
		String javaType = getQualifiedTypeName(type);
		return primitiveTypeOf(javaType, options);
	}

	/**
	 * This gets parameterized types of the given type substituting variable types if necessary
	 * @param type The raw type such as Batch<Item> or Batch<T, Y>
	 * @param varsToTypes A map of variable name to types
	 * @return The list of parameterized types
	 */
	public static List getParameterizedTypes(Type type, Map varsToTypes) {
		ParameterizedType pt = type.asParameterizedType();
		if (pt != null) {
			Type[] typeArgs = pt.typeArguments();
			if (typeArgs != null && typeArgs.length > 0) {
				List res = new ArrayList();
				for (Type pType : typeArgs) {
					Type replacedType = getVarType(pType.asTypeVariable(), varsToTypes);
					if (replacedType == null) {
						res.add(pType);
					} else {
						res.add(replacedType);
					}
				}
				return res;
			}
		}
		return Collections.emptyList();
	}

	/**
	 * This gets the type that a container holds
	 * @param type The raw type like Collection<String>
	 * @param varsToTypes A map of variables to types for parameterized types, optional if null parameterized types
	 *            will not be handled
	 * @param classes set of classes
	 * @return The container type or null if not a collection
	 */
	public static Type getContainerType(Type type, Map varsToTypes, Collection classes) {
		Type result = null;
		ParameterizedType pt = type.asParameterizedType();
		if (pt != null && (ParserHelper.isCollection(type.qualifiedTypeName()) || ParserHelper.isArray(type))) {
			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;
			}
		}
		// if its a non parameterized array then find the array type
		// note in java 8 we can do this directly, however for now the only solution is to look it up via the model classes
		if (ParserHelper.isArray(type)) {
			result = findModel(classes, type.qualifiedTypeName());
		}

		return result;
	}

	private static Map> CLASSES = new HashMap>();

	private static Class lookupClass(String javaType) throws ClassNotFoundException {
		if (CLASSES.containsKey(javaType)) {
			return CLASSES.get(javaType);
		}
		Class clazz = Class.forName(javaType);
		CLASSES.put(javaType, clazz);
		return clazz;
	}

	/**
	 * 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) {
			Set processedTypes = new HashSet();
			Type type = varsToTypes.get(var.qualifiedTypeName());
			while (type != null && !processedTypes.contains(type)) {
				res = type;
				processedTypes.add(type);
				type = varsToTypes.get(type.qualifiedTypeName());
			}
		}
		return res;
	}

	private static Map SET_TYPES = new HashMap();

	/**
	 * 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) {
		if (SET_TYPES.containsKey(javaType)) {
			return SET_TYPES.get(javaType);
		}
		try {
			Class clazz = lookupClass(javaType);
			boolean res = java.util.Set.class.isAssignableFrom(clazz);
			if (res) {
				SET_TYPES.put(javaType, res);
			}
			return res;
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	/**
	 * This gets whether the given type is an Array
	 * @param type The type
	 * @return True if this is an array
	 */
	public static boolean isArray(Type type) {
		return type.dimension() != null && type.dimension().length() > 0;
	}

	/**
	 * This gets whether the given type is an Array
	 * @param javaType The java type
	 * @return True if this is an array
	 */
	public static boolean isArray(String javaType) {
		return javaType.endsWith("[]");
	}

	private static Map COLLECTION_TYPES = new HashMap();

	/**
	 * 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) {
		if (COLLECTION_TYPES.containsKey(javaType)) {
			return COLLECTION_TYPES.get(javaType);
		}
		try {
			Class clazz = lookupClass(javaType);
			boolean res = java.util.Collection.class.isAssignableFrom(clazz);
			if (res) {
				COLLECTION_TYPES.put(javaType, res);
			}
			return res;
		} catch (ClassNotFoundException ex) {
			return false;
		}
	}

	private static Map MAP_TYPES = new HashMap();

	/**
	 * 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) {
		if (MAP_TYPES.containsKey(javaType)) {
			return MAP_TYPES.get(javaType);
		}
		try {
			Class clazz = lookupClass(javaType);
			boolean res = java.util.Map.class.isAssignableFrom(clazz);
			if (res) {
				MAP_TYPES.put(javaType, res);
			}
			return res;
		} 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) {
		if (hasAnnotation(parameter, options.getFileParameterAnnotations(), options)) {
			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) {
		if (hasAnnotation(paramMember, options.getFileParameterAnnotations(), options)) {
			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 rawName = parameter.name();
		AnnotationParser p = new AnnotationParser(parameter, options);
		return paramNameOf(rawName, p, overrideParamNames, paramNameAnnotations, options);
	}

	/**
	 * Determines the string representation of the parameter name.
	 * @param field The parameter field 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 fieldParamNameOf(FieldDoc field, Map overrideParamNames, List paramNameAnnotations, DocletOptions options) {

		String rawName = field.name();
		AnnotationParser p = new AnnotationParser(field, options);
		return paramNameOf(rawName, p, overrideParamNames, paramNameAnnotations, options);
	}

	private static String paramNameOf(String rawName, AnnotationParser p, Map overrideParamNames, List paramNameAnnotations,
			DocletOptions options) {
		String name = null;

		// 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) {
			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, it supports deriving the views from an overridden method
	 * @param methodDoc The method to get the json views of
	 * @param options The doclet options
	 * @return The json views for the given method/overridden method or null if there were none
	 */
	public static ClassDoc[] getInheritableJsonViews(MethodDoc methodDoc, DocletOptions options) {
		ClassDoc[] result = null;
		while (result == null && methodDoc != null) {
			result = getJsonViews(methodDoc, options);
			methodDoc = methodDoc.overriddenMethod();
		}
		return result;
	}

	/**
	 * 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) {
		List methodLevel = listInheritableValues(methodDoc, JAX_RS_CONSUMES, "value", options);
		if (methodLevel == null) {
			// look for class level
			return listValues(methodDoc.containingClass(), JAX_RS_CONSUMES, "value", options);
		}
		return methodLevel;
	}

	/**
	 * 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) {
		List methodLevel = listInheritableValues(methodDoc, JAX_RS_PRODUCES, "value", options);
		if (methodLevel == null) {
			// look for class level
			return listValues(methodDoc.containingClass(), JAX_RS_PRODUCES, "value", options);
		}
		return methodLevel;
	}

	/**
	 * This gets a list of values from an annotation that uses a string array value, it supports getting it from a superclass method
	 * @param methodDoc The method 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 listInheritableValues(ExecutableMemberDoc methodDoc, String qualifiedAnnotationType, String annotationValueName,
			DocletOptions options) {
		List result = null;
		while (result == null && methodDoc != null) {
			result = listValues(methodDoc, qualifiedAnnotationType, annotationValueName, options);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * 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;
		}
		String[] simpleType = primitiveTypeOf(type, options);
		return simpleType != null && PRIMITIVES.contains(simpleType[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;
		}
		String[] simpleType = primitiveTypeOf(type, options);
		return simpleType != null && PRIMITIVES.contains(simpleType[0]);
	}

	/**
	 * This gets whether the given type is a number
	 * @param type The type to check
	 * @param options The doclet options
	 * @return True if the given type is primitive
	 */
	public static boolean isNumber(Type type, DocletOptions options) {
		if (type == null) {
			return false;
		}
		String[] typeFormat = primitiveTypeOf(type, options);
		if (typeFormat != null) {
			String swaggerType = typeFormat[0];
			String format = typeFormat[1];

			if (swaggerType.equals("integer")) {
				return true;
			} else if (swaggerType.equals("number")) {
				return true;
			} else if (format != null && format.equals("byte")) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets whether the given method or an overridden method has any of the given tags
	 * @param methodDoc The method doc to get the tag value of
	 * @param matchTags The names of the tags to look for
	 * @return True if the method or an overridden method has any of the given tags
	 */
	public static boolean hasInheritableTag(ExecutableMemberDoc methodDoc, Collection matchTags) {
		boolean result = false;
		while (!result && methodDoc != null) {
			result = hasTag(methodDoc, matchTags);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * 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(ExecutableMemberDoc method) {
		Set params = new HashSet();
		for (Parameter parameter : method.parameters()) {
			params.add(parameter.name());
		}
		return params;
	}

	/**
	 * This gets params of the given method that have either any of the matching javadoc tags or annotations
	 * @param method The method
	 * @param params The pre-read params of the method, if null they will be read from the given method
	 * @param javadocTags Csv javadoc tags to look at
	 * @param annotations Annotations to look at
	 * @param options The doclet options
	 * @return A set of param names for the params that have either any of the matching javadoc tags or annotations
	 */
	public static Set getMatchingParams(ExecutableMemberDoc method, Set params, Collection javadocTags, Collection annotations,
			DocletOptions options) {

		Set res = new HashSet();

		// find params based on javadoc tags
		List javaDocParams = getCsvParams(method, params, javadocTags, options);
		res.addAll(javaDocParams);

		// add on params that have one of the param annotations
		Set annotationParams = getInheritableParametersWithAnnotation(method, annotations);
		res.addAll(annotationParams);

		return res;
	}

	private static Set getInheritableParametersWithAnnotation(ExecutableMemberDoc methodDoc, Collection annotations) {
		Set result = new HashSet();
		while (methodDoc != null) {
			Set subResult = getParametersWithAnnotation(methodDoc, annotations);
			result.addAll(subResult);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * This gets the names of parameters that have any of the given annotations on them
	 * @param method The method
	 * @param annotations The annotations to look for
	 * @return A set of param names with the given annotations
	 */
	private static Set getParametersWithAnnotation(ExecutableMemberDoc method, Collection annotations) {
		Set res = new HashSet();
		for (Parameter p : method.parameters()) {
			for (AnnotationDesc annotation : p.annotations()) {
				String qName = annotation.annotationType().qualifiedTypeName();
				if (annotations.contains(qName)) {
					res.add(p.name());
				}
			}
		}
		return res;
	}

	@SuppressWarnings("javadoc")
	public static interface TypeFilter {

		boolean matches(Type t);
	}

	@SuppressWarnings("javadoc")
	public static class NumericTypeFilter implements TypeFilter {

		private final DocletOptions options;

		public NumericTypeFilter(DocletOptions options) {
			super();
			this.options = options;
		}

		public boolean matches(Type t) {
			return ParserHelper.isNumber(t, this.options);
		}

	}

	/**
	 * This gets the first met parameter with annotations from the method overriding hierarchy
	 * @param methodDoc The method
	 * @param paramIndex Parameter index
	 * @return A parameter from the method overriding hierarchy with annotations
	 */
	public static Parameter getParameterWithAnnotations(ExecutableMemberDoc methodDoc, int paramIndex) {
		final Parameter fallbackParameter = methodDoc.parameters()[paramIndex];
		Parameter parameter;
		boolean found;
		do {
			parameter = methodDoc.parameters()[paramIndex];
			found = (parameter.annotations() != null && parameter.annotations().length > 0);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		} while (methodDoc != null && !found);
		return (found) ? parameter : fallbackParameter;
	}

	/**
	 * This gets the values of parameters from either javadoc tags or annotations
	 * @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 annotations The annotations to look for
	 * @param annotationTypes The types that the annotations should apply to
	 * @param options The doclet options (used for var replacement)
	 * @param valueKeys The attribute names to look for on the annotations as the value
	 * @return A set of param names with the given annotations
	 */
	public static Map getParameterValues(ExecutableMemberDoc method, Set params, Collection matchTags,
			Collection annotations, TypeFilter annotationTypes, DocletOptions options, String... valueKeys) {
		Map res = new HashMap();
		// first add values from javadoc tags
		Map javadocVals = getMethodParamNameValuePairs(method, params, matchTags, options);
		res.putAll(javadocVals);
		// add values from annotations
		Map annotationVals = getParameterValuesWithAnnotation(method, annotations, annotationTypes, options, valueKeys);
		res.putAll(annotationVals);
		return res;
	}

	/**
	 * This gets the values of annotations of parameters that have any of the given annotations on them
	 * @param methodDoc The method
	 * @param annotations The annotations to look for
	 * @param annotationTypes The types that the annotations should apply to
	 * @param options The doclet options (used for var replacement)
	 * @param valueKeys The attribute names to look for on the annotations as the value
	 * @return A set of param names with the given annotations
	 */
	public static Map getParameterValuesWithAnnotation(ExecutableMemberDoc methodDoc, Collection annotations,
			TypeFilter annotationTypes, DocletOptions options, String... valueKeys) {
		Map res = new HashMap();
		while (methodDoc != null) {
			for (Parameter p : methodDoc.parameters()) {
				String value = new AnnotationParser(p, options).getAnnotationValue(annotations, valueKeys);
				if (value != null && (annotationTypes == null || annotationTypes.matches(p.type()))) {
					res.put(p.name(), value);
				}
			}
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return res;
	}

	/**
	 * 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(ExecutableMemberDoc method, Set params, Collection matchTags,
			DocletOptions options) {

		if (params == null) {
			params = getParamNames(method);
		}

		// add name to value pairs from any matching javadoc tag on the method
		String value = getInheritableTagValue(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.");
				}
				Map res = new HashMap();
				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 map of parameter name to list of values from a javadoc tag on a method.
	 * This validates that the names of the parameters in each name to list 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 list of values from a javadoc tag on a method or an empty map if none were found
	 */
	public static Map> getMethodParamNameValueLists(ExecutableMemberDoc method, Set params, Collection matchTags,
			DocletOptions options) {

		if (params == null) {
			params = getParamNames(method);
		}

		// add name to value pairs from any matching javadoc tag on the method
		String value = getInheritableTagValue(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.");
				}
				Map> res = new HashMap>();
				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];

					List vals = res.get(name);
					if (vals == null) {
						vals = new ArrayList();
						res.put(name, vals);
					}
					vals.add(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(ExecutableMemberDoc 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(ExecutableMemberDoc item, Collection matchTags, DocletOptions options) {
		String value = getInheritableTagValue(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 first value on the given item (field or method) that matches either an annotation or a javadoc tag.
	 * This does NOT support method overriding
	 * @param item The javadoc item
	 * @param annotations The FQN of the annotations to check
	 * @param matchTags The collection of tag names of the tag to get a value of
	 * @param options The doclet options
	 * @param valueKeys The names of the attributes of the annotations to look at
	 * @return The value or null if none was found
	 */
	public static String getAnnotationOrTagValue(com.sun.javadoc.ProgramElementDoc item, Collection annotations, Collection matchTags,
			DocletOptions options, String... valueKeys) {

		// first check for an annotation value
		AnnotationParser p = new AnnotationParser(item, options);
		String annotationVal = p.getAnnotationValue(annotations, valueKeys);
		if (annotationVal != null) {
			return annotationVal;
		}

		// now check for a tag value
		return getTagValue(item, matchTags, options);
	}

	/**
	 * Resolves HttpMethod for the MethodDoc respecting the overriden methods
	 * @param methodDoc The method to be processed
	 * @return The resolved HttpMethod
	 */
	public static HttpMethod resolveMethodHttpMethod(MethodDoc methodDoc) {
		HttpMethod result = null;
		while (result == null && methodDoc != null) {
			result = HttpMethod.fromMethod(methodDoc);
			methodDoc = methodDoc.overriddenMethod();
		}
		return result;
	}

	/**
	 * This gets the first sentence tags of a method or its overridden ancestor method
	 * @param methodDoc The method
	 * @return The first sentence tag or null if there is none
	 */
	public static String getInheritableFirstSentenceTags(ExecutableMemberDoc methodDoc) {
		String result = null;
		while (result == null && methodDoc != null) {

			Tag[] fst = methodDoc.firstSentenceTags();
			if (fst != null && fst.length > 0) {
				StringBuilder sentences = new StringBuilder();
				for (Tag tag : fst) {
					sentences.append(tag.text());
				}
				String firstSentences = sentences.toString();
				result = firstSentences;
			}

			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * This gets the first sentence tags of a method or its overridden ancestor method
	 * @param methodDoc The method
	 * @return The first sentence tag or null if there is none
	 */
	public static String getInheritableCommentText(ExecutableMemberDoc methodDoc) {
		String result = null;
		while (result == null && methodDoc != null) {

			String commentText = methodDoc.commentText();
			if (commentText != null && !commentText.isEmpty()) {
				result = commentText;
			}

			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * This gets values of any of the javadoc tags that are in the given collection from the method or overridden methods
	 * @param methodDoc The javadoc method 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 getInheritableTagValues(ExecutableMemberDoc methodDoc, Collection matchTags, DocletOptions options) {
		List result = null;
		while (result == null && methodDoc != null) {
			result = getTagValues(methodDoc, matchTags, options);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * This gets an annotation value from the given class, it supports looking at super classes
	 * @param classDoc The class to look for the annotation
	 * @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 annotation or null if none was found
	 */
	public static List getInheritableTagValues(ClassDoc classDoc, Collection matchTags, DocletOptions options) {
		ClassDoc currentClassDoc = classDoc;
		while (currentClassDoc != null) {
			List values = getTagValues(currentClassDoc, matchTags, options);

			if (values != null) {
				return values;
			}

			currentClassDoc = currentClassDoc.superclass();
			// ignore parent object class
			if (!ParserHelper.hasAncestor(currentClassDoc)) {
				break;
			}
		}
		return null;
	}

	/**
	 * 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) {
			Tag[] tags = item.tags();
			if (tags != null && tags.length > 0) {
				for (Tag tag : tags) {
					if (matchTags.contains(tag.name().substring(1))) {
						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 gets the value of the first tag found from the given MethodDoc respecting the overriden methods
	 * @param methodDoc The method doc 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 getInheritableTagValue(ExecutableMemberDoc methodDoc, Collection matchTags, DocletOptions options) {
		String result = null;
		while (result == null && methodDoc != null) {
			result = getTagValue(methodDoc, matchTags, options);
			methodDoc = methodDoc instanceof MethodDoc ? ((MethodDoc) methodDoc).overriddenMethod() : null;
		}
		return result;
	}

	/**
	 * This gets an annotation value from the given class, it supports looking at super classes
	 * @param classDoc The class to look for the annotation
	 * @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 annotation or null if none was found
	 */
	public static String getInheritableTagValue(ClassDoc classDoc, Collection matchTags, DocletOptions options) {
		ClassDoc currentClassDoc = classDoc;
		while (currentClassDoc != null) {
			String value = getTagValue(currentClassDoc, matchTags, options);

			if (value != null) {
				return value;
			}

			currentClassDoc = currentClassDoc.superclass();
			// ignore parent object class
			if (!ParserHelper.hasAncestor(currentClassDoc)) {
				break;
			}
		}
		return null;
	}

	/**
	 * 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 method or an overridden method has any of the given annotations
	 * @param methodDoc The method doc to check
	 * @param annotations the annotations to check
	 * @param options The doclet options
	 * @return True if the method or an overridden method has one of the given annotations
	 */
	public static boolean hasInheritableAnnotation(MethodDoc methodDoc, Collection annotations, DocletOptions options) {
		boolean result = false;
		while (!result && methodDoc != null) {
			result = hasAnnotation(methodDoc, annotations, options);
			methodDoc = methodDoc.overriddenMethod();
		}
		return result;
	}

	/**
	 * This gets whether the given item has one of the given annotations
	 * @param item The field/method to check
	 * @param annotations the annotations to check
	 * @param options The doclet options
	 * @return True if the item has one of the given annotations
	 */
	public static boolean hasAnnotation(com.sun.javadoc.ProgramElementDoc item, Collection annotations, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(item, options);
		for (String annotation : annotations) {
			if (p.isAnnotatedBy(annotation)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets whether the given parameter has one of the given annotations
	 * @param item The field/method to check
	 * @param annotations the annotations to check
	 * @param options The doclet options
	 * @return True if the parameter has one of the given annotations
	 */
	public static boolean hasAnnotation(Parameter item, Collection annotations, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(item, options);
		for (String annotation : annotations) {
			if (p.isAnnotatedBy(annotation)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets whether the given parameter has an annotation whose FQN begins with one of the given prefixes
	 * @param item The field/method to check
	 * @param prefixes the prefixes to check for
	 * @param options The doclet options
	 * @return True if the parameter has an annotation whose FQN begins with one of the given prefixes
	 */
	public static boolean hasAnnotationWithPrefix(Parameter item, Collection prefixes, DocletOptions options) {
		AnnotationParser p = new AnnotationParser(item, options);
		for (String prefix : prefixes) {
			if (p.isAnnotatedByPrefix(prefix)) {
				return true;
			}
		}
		return false;
	}

	/**
	 * This gets whether the given parameter has a JAXRS annotation
	 * @param item The parameter
	 * @param options The doclet options
	 * @return True if the parameter has the given annotation
	 */
	public static boolean hasJaxRsAnnotation(Parameter item, DocletOptions options) {
		return hasAnnotationWithPrefix(item, JAX_RS_PREFIXES, options);
	}

	/**
	 * This gets whether the given item is marked as deprecated either via a javadoc tag
	 * or an annotation, this supports looking at overridden methods
	 * @param item The item to check
	 * @param options The doclet options
	 * @return True if the item is flagged as deprecated
	 */
	public static boolean isInheritableDeprecated(com.sun.javadoc.MethodDoc item, DocletOptions options) {
		if (hasInheritableTag(item, DEPRECATED_TAGS)) {
			return true;
		}
		return hasInheritableAnnotation(item, DEPRECATED_ANNOTATIONS, options);
	}

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

	/**
	 * This gets whether the given parameter is marked as deprecated either via a javadoc tag
	 * or an annotation
	 * @param parameter The parameter to check
	 * @param options The doclet options
	 * @return True if the parameter is flagged as deprecated
	 */
	public static boolean isDeprecated(com.sun.javadoc.Parameter parameter, DocletOptions options) {
		return hasAnnotation(parameter, DEPRECATED_ANNOTATIONS, options);
	}

	/**
	 * 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;
	}

	static final Map PRIMITIVE_TO_CLASS = new HashMap();
	static {
		PRIMITIVE_TO_CLASS.put("int", java.lang.Integer.class.getName());
		PRIMITIVE_TO_CLASS.put("boolean", java.lang.Boolean.class.getName());
		PRIMITIVE_TO_CLASS.put("float", java.lang.Float.class.getName());
		PRIMITIVE_TO_CLASS.put("double", java.lang.Double.class.getName());
		PRIMITIVE_TO_CLASS.put("char", java.lang.Character.class.getName());
		PRIMITIVE_TO_CLASS.put("long", java.lang.Long.class.getName());
		PRIMITIVE_TO_CLASS.put("byte", java.lang.Byte.class.getName());
		PRIMITIVE_TO_CLASS.put("string", java.lang.String.class.getName());

		PRIMITIVE_TO_CLASS.put(java.math.BigDecimal.class.getName(), java.lang.Double.class.getName());
		PRIMITIVE_TO_CLASS.put(java.math.BigInteger.class.getName(), java.lang.Long.class.getName());
	}

	/**
	 * 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) {
			// map primitives to their class equiv
			if (PRIMITIVE_TO_CLASS.containsKey(qualifiedClassName)) {
				qualifiedClassName = PRIMITIVE_TO_CLASS.get(qualifiedClassName);
			}
			for (ClassDoc cls : classes) {
				if (qualifiedClassName.equals(cls.qualifiedName())) {
					return cls;
				}
			}
		}
		return null;
	}

	/**
	 * This generates a file name to use for a resource with the given resource path.
	 * It ensures that any non filename safe characters are removed
	 * For example /api/test/{id:[0-9]+} will become api_test_id
	 * @param resourcePath The path to sanitize
	 * @return The resource path sanitized
	 */
	public static String generateResourceFilename(String resourcePath) {

		// first remove regex expressions so we end up for example
		// instead of /api/test/{id:[0-9]+} with /api/test/{id}
		String sanitized = sanitizePath(resourcePath)
		// now replace / and {} with underscores
				.replace("{", "_").replace("}", "").replace("/", "_")
				// and finally swap multiple underscores with a single one
				.replaceAll("[_]+", "_");

		// remove any trailing or leading underscores
		if (sanitized.endsWith("_")) {
			sanitized = sanitized.substring(0, sanitized.length() - 1);
		}
		if (sanitized.startsWith("_")) {
			sanitized = sanitized.substring(1);
		}

		return sanitized;

	}

	/**
	 * This sanitizes an API path. It handles removing regex path expressions
	 * e.g instead of /api/test/{id:[0-9]+} it will return /api/test/{id}
	 * @param apiPath The api path to sanitize
	 * @return The sanitized path
	 */
	public static String sanitizePath(String apiPath) {
		return apiPath.replaceAll("[: ]+.*?}", "}");
	}

	/**
	 * This trims specific characters from the start of the given string
	 * @param str The string to trim
	 * @param trimChars The characters to trim from the string
	 * @return The string with any of the trimable characters removed from the start of the string
	 */
	public static String trimLeadingChars(String str, char... trimChars) {
		if (str == null || str.trim().isEmpty()) {
			return str;
		}
		StringBuilder newStr = new StringBuilder();
		boolean foundNonTrimChar = false;
		for (int i = 0; i < str.length(); i++) {
			char c = str.charAt(i);
			if (foundNonTrimChar) {
				newStr.append(c);
			} else {
				if (Character.isWhitespace(c)) {
					// trim
				} else {
					// see if a non trim char, if so add it and set flag
					boolean isTrimChar = false;
					for (char trimC : trimChars) {
						if (c == trimC) {
							isTrimChar = true;
							break;
						}
					}
					if (!isTrimChar) {
						foundNonTrimChar = true;
						newStr.append(c);
					}
				}
			}
		}
		return newStr.length() == 0 ? null : newStr.toString().trim();
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy