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

com.fasterxml.jackson.jaxrs.base.ProviderBase Maven / Gradle / Ivy

There is a newer version: 1.5.7
Show newest version
package com.fasterxml.jackson.jaxrs.base;

import java.io.*;
import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.Type;
import java.util.*;
import java.util.concurrent.atomic.AtomicReference;

import javax.ws.rs.core.*;
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;

import com.fasterxml.jackson.core.*;
import com.fasterxml.jackson.databind.*;
import com.fasterxml.jackson.jaxrs.cfg.*;
import com.fasterxml.jackson.jaxrs.util.ClassKey;
import com.fasterxml.jackson.jaxrs.util.LRUMap;

public abstract class ProviderBase<
    THIS extends ProviderBase,
    MAPPER extends ObjectMapper,
    EP_CONFIG extends EndpointConfigBase,
    MAPPER_CONFIG extends MapperConfiguratorBase
>
    implements
        MessageBodyReader,
        MessageBodyWriter,
        Versioned
{
    /**
     * This header is useful on Windows, trying to deal with potential XSS attacks.
     */
    public final static String HEADER_CONTENT_TYPE_OPTIONS = "X-Content-Type-Options";

    /**
     * Since class javax.ws.rs.core.NoContentException only exists in
     * JAX-RS 2.0, but we need 1.1 compatibility, need to (unfortunately!) dynamically
     * load class.
     */
    protected final static String CLASS_NAME_NO_CONTENT_EXCEPTION = "javax.ws.rs.core.NoContentException";

    protected final static String NO_CONTENT_MESSAGE = "No content (empty input stream)";
    
    /**
     * Looks like we need to worry about accidental
     *   data binding for types we shouldn't be handling. This is
     *   probably not a very good way to do it, but let's start by
     *   blacklisting things we are not to handle.
     *

* (why ClassKey? since plain old Class has no hashCode() defined, * lookups are painfully slow) */ protected final static HashSet DEFAULT_UNTOUCHABLES = new HashSet(); static { // First, I/O things (direct matches) DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.InputStream.class)); DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.Reader.class)); DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.OutputStream.class)); DEFAULT_UNTOUCHABLES.add(new ClassKey(java.io.Writer.class)); // then some primitive types DEFAULT_UNTOUCHABLES.add(new ClassKey(char[].class)); /* 27-Apr-2012, tatu: Ugh. As per * [https://github.com/FasterXML/jackson-jaxrs-json-provider/issues/12] * better revert this back, to make them untouchable again. */ DEFAULT_UNTOUCHABLES.add(new ClassKey(String.class)); DEFAULT_UNTOUCHABLES.add(new ClassKey(byte[].class)); } /** * These are classes that we never use for reading * (never try to deserialize instances of these types). */ public final static Class[] DEFAULT_UNREADABLES = new Class[] { InputStream.class, Reader.class }; /** * These are classes that we never use for writing * (never try to serialize instances of these types). */ public final static Class[] DEFAULT_UNWRITABLES = new Class[] { InputStream.class, // as per [Issue#19] OutputStream.class, Writer.class, StreamingOutput.class, Response.class }; protected final static int JAXRS_FEATURE_DEFAULTS = JaxRSFeature.collectDefaults(); /* /********************************************************** /* General configuration /********************************************************** */ /** * Helper object used for encapsulating configuration aspects * of {@link ObjectMapper} */ protected final MAPPER_CONFIG _mapperConfig; /** * Map that contains overrides to default list of untouchable * types: true meaning that entry is untouchable, * false that is is not. */ protected HashMap _cfgCustomUntouchables; /** * Whether we want to actually check that Jackson has * a serializer for given type. Since this should generally * be the case (due to auto-discovery) and since the call * to check availability can be bit expensive, defaults to false. */ protected boolean _cfgCheckCanSerialize = false; /** * Whether we want to actually check that Jackson has * a deserializer for given type. Since this should generally * be the case (due to auto-discovery) and since the call * to check availability can be bit expensive, defaults to false. */ protected boolean _cfgCheckCanDeserialize = false; /** * Feature flags set. * * @since 2.3 */ protected int _jaxRSFeatures; /** * View to use for reading if none defined for the end point. */ protected Class _defaultReadView; /** * View to use for writing if none defined for the end point. */ protected Class _defaultWriteView; /* /********************************************************** /* Excluded types /********************************************************** */ public final static HashSet _untouchables = DEFAULT_UNTOUCHABLES; public final static Class[] _unreadableClasses = DEFAULT_UNREADABLES; public final static Class[] _unwritableClasses = DEFAULT_UNWRITABLES; /* /********************************************************** /* Bit of caching /********************************************************** */ /** * Cache for resolved endpoint configurations when reading JSON data */ protected final LRUMap _readers = new LRUMap(16, 120); /** * Cache for resolved endpoint configurations when writing JSON data */ protected final LRUMap _writers = new LRUMap(16, 120); protected final AtomicReference _noContentExceptionRef = new AtomicReference(); /* /********************************************************** /* Life-cycle /********************************************************** */ protected ProviderBase(MAPPER_CONFIG mconfig) { _mapperConfig = mconfig; _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS; } /** * Constructor that is only added to resolve * issue #10; problems with combination of * RESTeasy and CDI. * Should NOT be used by any code explicitly; only exists * for proxy support. */ @Deprecated // just to denote it should NOT be directly called; will NOT be removed protected ProviderBase() { _mapperConfig = null; _jaxRSFeatures = JAXRS_FEATURE_DEFAULTS; } /* /********************************************************** /* Configuring /********************************************************** */ /** * Method for defining whether actual detection for existence of * a deserializer for type should be done when {@link #isReadable} * is called. */ public void checkCanDeserialize(boolean state) { _cfgCheckCanDeserialize = state; } /** * Method for defining whether actual detection for existence of * a serializer for type should be done when {@link #isWriteable} * is called. */ public void checkCanSerialize(boolean state) { _cfgCheckCanSerialize = state; } /** * Method for marking specified type as "untouchable", meaning that provider * will not try to read or write values of this type (or its subtypes). * * @param type Type to consider untouchable; can be any kind of class, * including abstract class or interface. No instance of this type * (including subtypes, i.e. types assignable to this type) will * be read or written by provider */ public void addUntouchable(Class type) { if (_cfgCustomUntouchables == null) { _cfgCustomUntouchables = new HashMap(); } _cfgCustomUntouchables.put(new ClassKey(type), Boolean.TRUE); } /** * Method for removing definition of specified type as untouchable: * usually only * * @since 2.2 */ public void removeUntouchable(Class type) { if (_cfgCustomUntouchables == null) { _cfgCustomUntouchables = new HashMap(); } _cfgCustomUntouchables.put(new ClassKey(type), Boolean.FALSE); } /** * Method for configuring which annotation sets to use (including none). * Annotation sets are defined in order decreasing precedence; that is, * first one has the priority over following ones. * * @param annotationsToUse Ordered list of annotation sets to use; if null, * default */ public void setAnnotationsToUse(Annotations[] annotationsToUse) { _mapperConfig.setAnnotationsToUse(annotationsToUse); } /** * Method that can be used to directly define {@link ObjectMapper} to use * for serialization and deserialization; if null, will use the standard * provider discovery from context instead. Default setting is null. */ public void setMapper(MAPPER m) { _mapperConfig.setMapper(m); } /** * Method for specifying JSON View to use for reading content * when end point does not have explicit View annotations. * * @since 2.3 */ public THIS setDefaultReadView(Class view) { _defaultReadView = view; return _this(); } /** * Method for specifying JSON View to use for reading content * when end point does not have explicit View annotations. * * @since 2.3 */ public THIS setDefaultWriteView(Class view) { _defaultWriteView = view; return _this(); } /** * Method for specifying JSON View to use for reading and writing content * when end point does not have explicit View annotations. * Functionally equivalent to: * * setDefaultReadView(view); * setDefaultWriteView(view); * * * @since 2.3 */ public THIS setDefaultView(Class view) { _defaultReadView = _defaultWriteView = view; return _this(); } // // // JaxRSFeature config public THIS configure(JaxRSFeature feature, boolean state) { _jaxRSFeatures |= feature.getMask(); return _this(); } public THIS enable(JaxRSFeature feature) { _jaxRSFeatures |= feature.getMask(); return _this(); } public THIS enable(JaxRSFeature first, JaxRSFeature... f2) { _jaxRSFeatures |= first.getMask(); for (JaxRSFeature f : f2) { _jaxRSFeatures |= f.getMask(); } return _this(); } public THIS disable(JaxRSFeature feature) { _jaxRSFeatures &= ~feature.getMask(); return _this(); } public THIS disable(JaxRSFeature first, JaxRSFeature... f2) { _jaxRSFeatures &= ~first.getMask(); for (JaxRSFeature f : f2) { _jaxRSFeatures &= ~f.getMask(); } return _this(); } public boolean isEnabled(JaxRSFeature f) { return (_jaxRSFeatures & f.getMask()) != 0; } // // // DeserializationFeature public THIS configure(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, state); return _this(); } public THIS enable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, true); return _this(); } public THIS disable(DeserializationFeature f, boolean state) { _mapperConfig.configure(f, false); return _this(); } // // // SerializationFeature public THIS configure(SerializationFeature f, boolean state) { _mapperConfig.configure(f, state); return _this(); } public THIS enable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, true); return _this(); } public THIS disable(SerializationFeature f, boolean state) { _mapperConfig.configure(f, false); return _this(); } // // // JsonParser/JsonGenerator public THIS enable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, true); return _this(); } public THIS enable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, true); return _this(); } public THIS disable(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, false); return _this(); } public THIS disable(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, false); return _this(); } public THIS configure(JsonParser.Feature f, boolean state) { _mapperConfig.configure(f, state); return _this(); } public THIS configure(JsonGenerator.Feature f, boolean state) { _mapperConfig.configure(f, state); return _this(); } /* /********************************************************** /* Abstract methods sub-classes need to implement /********************************************************** */ /** * Helper method used to check whether given media type * is supported by this provider for read operations * (when binding input data such as POST body). *

* Default implementation simply calls {@link #hasMatchingMediaType}. * * @since 2.3 */ protected boolean hasMatchingMediaTypeForReading(MediaType mediaType) { return hasMatchingMediaType(mediaType); } /** * Helper method used to check whether given media type * is supported by this provider for writing operations, * such as when converting response object to response * body of request (like GET or POST). *

* Default implementation simply calls {@link #hasMatchingMediaType}. * * @since 2.3 */ protected boolean hasMatchingMediaTypeForWriting(MediaType mediaType) { return hasMatchingMediaType(mediaType); } /** * Helper method used to check whether given media type * is supported by this provider. * * @since 2.2 */ protected abstract boolean hasMatchingMediaType(MediaType mediaType); protected abstract MAPPER _locateMapperViaProvider(Class type, MediaType mediaType); protected EP_CONFIG _configForReading(MAPPER mapper, Annotation[] annotations, Class defaultView) { // ObjectReader r = _readerInjector.getAndClear(); ObjectReader r; if (defaultView != null) { r = mapper.readerWithView(defaultView); } else { r = mapper.reader(); } return _configForReading(r, annotations); } protected EP_CONFIG _configForWriting(MAPPER mapper, Annotation[] annotations, Class defaultView) { // ObjectWriter w = _writerInjector.getAndClear(); ObjectWriter w; if (defaultView != null) { w = mapper.writerWithView(defaultView); } else { w = mapper.writer(); } return _configForWriting(w, annotations); } /** * @deprecated Since 2.3, use variant that takes explicit defaultView */ @Deprecated protected EP_CONFIG _configForReading(MAPPER mapper, Annotation[] annotations) { return _configForReading(mapper, annotations, _defaultReadView); } /** * @deprecated Since 2.3, use variant that takes explicit defaultView */ @Deprecated protected EP_CONFIG _configForWriting(MAPPER mapper, Annotation[] annotations) { return _configForWriting(mapper, annotations, _defaultWriteView); } protected abstract EP_CONFIG _configForReading(ObjectReader reader, Annotation[] annotations); protected abstract EP_CONFIG _configForWriting(ObjectWriter writer, Annotation[] annotations); /* /********************************************************** /* Partial MessageBodyWriter impl /********************************************************** */ /** * Method that JAX-RS container calls to try to figure out * serialized length of given value. Since computation of * this length is about as expensive as serialization itself, * implementation will return -1 to denote "not known", so * that container will determine length from actual serialized * output (if needed). */ @Override public long getSize(Object value, Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { /* In general figuring output size requires actual writing; usually not * worth it to write everything twice. */ return -1; } /** * Method that JAX-RS container calls to try to check whether * given value (of specified type) can be serialized by * this provider. * Implementation will first check that expected media type is * expected one (by call to {@link #hasMatchingMediaType}); then verify * that type is not one of "untouchable" types (types we will never * automatically handle), and finally that there is a serializer * for type (iff {@link #checkCanSerialize} has been called with * true argument -- otherwise assumption is there will be a handler) */ @Override public boolean isWriteable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { if (!hasMatchingMediaType(mediaType)) { return false; } Boolean customUntouchable = _findCustomUntouchable(type); if (customUntouchable != null) { // negation: Boolean.TRUE means untouchable -> can not write return !customUntouchable.booleanValue(); } /* Ok: looks like we must weed out some core types here; ones that * make no sense to try to bind from JSON: */ if (_untouchables.contains(new ClassKey(type))) { return false; } // but some are interface/abstract classes, so for (Class cls : _unwritableClasses) { if (cls.isAssignableFrom(type)) { return false; } } // Also: if we really want to verify that we can deserialize, we'll check: if (_cfgCheckCanSerialize) { if (!locateMapper(type, mediaType).canSerialize(type)) { return false; } } return true; } /** * Method that JAX-RS container calls to serialize given value. */ @Override public void writeTo(Object value, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException { AnnotationBundleKey key = new AnnotationBundleKey(annotations, type); EP_CONFIG endpoint; synchronized (_writers) { endpoint = _writers.get(key); } // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { MAPPER mapper = locateMapper(type, mediaType); endpoint = _configForWriting(mapper, annotations, _defaultWriteView); // and cache for future reuse synchronized (_writers) { _writers.put(key.immutableKey(), endpoint); } } // Any headers we should write? _modifyHeaders(value, type, genericType, annotations, httpHeaders, endpoint); ObjectWriter writer = endpoint.getWriter(); // Where can we find desired encoding? Within HTTP headers? JsonEncoding enc = findEncoding(mediaType, httpHeaders); JsonGenerator g = _createGenerator(writer, entityStream, enc); boolean ok = false; try { // Want indentation? if (writer.isEnabled(SerializationFeature.INDENT_OUTPUT)) { g.useDefaultPrettyPrinter(); } JavaType rootType = null; if (genericType != null && value != null) { /* 10-Jan-2011, tatu: as per [JACKSON-456], it's not safe to just force root * type since it prevents polymorphic type serialization. Since we really * just need this for generics, let's only use generic type if it's truly * generic. */ if (genericType.getClass() != Class.class) { // generic types are other impls of 'java.lang.reflect.Type' /* This is still not exactly right; should root type be further * specialized with 'value.getClass()'? Let's see how well this works before * trying to come up with more complete solution. */ rootType = writer.getTypeFactory().constructType(genericType); /* 26-Feb-2011, tatu: To help with [JACKSON-518], we better recognize cases where * type degenerates back into "Object.class" (as is the case with plain TypeVariable, * for example), and not use that. */ if (rootType.getRawClass() == Object.class) { rootType = null; } } } // Most of the configuration now handled through EndpointConfig, ObjectWriter // but we may need to force root type: if (rootType != null) { writer = writer.withType(rootType); } value = endpoint.modifyBeforeWrite(value); // [Issue#32]: allow modification by filter-injectible thing ObjectWriterModifier mod = ObjectWriterInjector.getAndClear(); if (mod != null) { writer = mod.modify(endpoint, httpHeaders, value, writer, g); } writer.writeValue(g, value); ok = true; } finally { if (ok) { g.close(); } else { try { g.close(); } catch (Exception e) { } } } } /** * Helper method to use for determining desired output encoding. * For now, will always just use UTF-8... */ protected JsonEncoding findEncoding(MediaType mediaType, MultivaluedMap httpHeaders) { return JsonEncoding.UTF8; } /** * Overridable method used for adding optional response headers before * serializing response object. */ protected void _modifyHeaders(Object value, Class type, Type genericType, Annotation[] annotations, MultivaluedMap httpHeaders, EP_CONFIG endpoint) throws IOException { // [Issue#6]: Add "nosniff" header? if (isEnabled(JaxRSFeature.ADD_NO_SNIFF_HEADER)) { httpHeaders.add(HEADER_CONTENT_TYPE_OPTIONS, "nosniff"); } } /** * Overridable helper method called to create a {@link JsonGenerator} for writing * contents into given raw {@link OutputStream}. * * @since 2.3 */ protected JsonGenerator _createGenerator(ObjectWriter writer, OutputStream rawStream, JsonEncoding enc) throws IOException { JsonGenerator g = writer.getFactory().createGenerator(rawStream, enc); // Important: we are NOT to close the underlying stream after // mapping, so we need to instruct generator g.disable(JsonGenerator.Feature.AUTO_CLOSE_TARGET); return g; } /* /********************************************************** /* MessageBodyReader impl /********************************************************** */ /** * Method that JAX-RS container calls to try to check whether * values of given type (and media type) can be deserialized by * this provider. * Implementation will first check that expected media type is * a JSON type (via call to {@link #hasMatchingMediaType}); * then verify * that type is not one of "untouchable" types (types we will never * automatically handle), and finally that there is a deserializer * for type (iff {@link #checkCanDeserialize} has been called with * true argument -- otherwise assumption is there will be a handler) */ @Override public boolean isReadable(Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { if (!hasMatchingMediaType(mediaType)) { return false; } Boolean customUntouchable = _findCustomUntouchable(type); if (customUntouchable != null) { // negation: Boolean.TRUE means untouchable -> can not write return !customUntouchable.booleanValue(); } /* Ok: looks like we must weed out some core types here; ones that * make no sense to try to bind from JSON: */ if (_untouchables.contains(new ClassKey(type))) { return false; } // and there are some other abstract/interface types to exclude too: for (Class cls : _unreadableClasses) { if (cls.isAssignableFrom(type)) { return false; } } // Finally: if we really want to verify that we can serialize, we'll check: if (_cfgCheckCanSerialize) { if (_isSpecialReadable(type)) { return true; } ObjectMapper mapper = locateMapper(type, mediaType); if (!mapper.canDeserialize(mapper.constructType(type))) { return false; } } return true; } /** * Method that JAX-RS container calls to deserialize given value. */ @Override public Object readFrom(Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException { AnnotationBundleKey key = new AnnotationBundleKey(annotations, type); EP_CONFIG endpoint; synchronized (_readers) { endpoint = _readers.get(key); } // not yet resolved (or not cached any more)? Resolve! if (endpoint == null) { MAPPER mapper = locateMapper(type, mediaType); endpoint = _configForReading(mapper, annotations, _defaultReadView); // and cache for future reuse synchronized (_readers) { _readers.put(key.immutableKey(), endpoint); } } ObjectReader reader = endpoint.getReader(); JsonParser jp = _createParser(reader, entityStream); // If null is returned, considered to be empty stream // 05-Apr-2014, tatu: As per [Issue#49], behavior here is configurable. if (jp == null || jp.nextToken() == null) { if (JaxRSFeature.ALLOW_EMPTY_INPUT.enabledIn(_jaxRSFeatures)) { return null; } /* 05-Apr-2014, tatu: Trick-ee. NoContentFoundException only available in JAX-RS 2.0... * so need bit of obfuscated code to reach it. */ IOException fail = _noContentExceptionRef.get(); if (fail == null) { fail = _createNoContentException(); } throw fail; } // [Issue#1]: allow 'binding' to JsonParser if (((Class) type) == JsonParser.class) { return jp; } final JavaType resolvedType = reader.getTypeFactory().constructType(genericType); reader = reader.withType(resolvedType); // [Issue#32]: allow modification by filter-injectible thing ObjectReaderModifier mod = ObjectReaderInjector.getAndClear(); if (mod != null) { reader = mod.modify(endpoint, httpHeaders, resolvedType, reader, jp); } return reader.readValue(jp); } /** * Overridable helper method called to create a {@link JsonParser} for reading * contents of given raw {@link InputStream}. * May return null to indicate that Stream is empty; that is, contains no * content. * * @since 2.2 */ protected JsonParser _createParser(ObjectReader reader, InputStream rawStream) throws IOException { JsonParser p = reader.getFactory().createParser(rawStream); // Important: we are NOT to close the underlying stream after // mapping, so we need to instruct parser: p.disable(JsonParser.Feature.AUTO_CLOSE_SOURCE); return p; } /* /********************************************************** /* Overridable helper methods /********************************************************** */ /** * Method called to locate {@link ObjectMapper} to use for serialization * and deserialization. If an instance has been explicitly defined by * {@link #setMapper} (or non-null instance passed in constructor), that * will be used. * If not, will try to locate it using standard JAX-RS * ContextResolver mechanism, if it has been properly configured * to access it (by JAX-RS runtime). * Finally, if no mapper is found, will return a default unconfigured * {@link ObjectMapper} instance (one constructed with default constructor * and not modified in any way) * * @param type Class of object being serialized or deserialized; * not checked at this point, since it is assumed that unprocessable * classes have been already weeded out, * but will be passed to ContextResolver as is. * @param mediaType Declared media type for the instance to process: * not used by this method, * but will be passed to ContextResolver as is. */ public MAPPER locateMapper(Class type, MediaType mediaType) { // First: were we configured with a specific instance? MAPPER m = _mapperConfig.getConfiguredMapper(); if (m == null) { // If not, maybe we can get one configured via context? m = _locateMapperViaProvider(type, mediaType); if (m == null) { // If not, let's get the fallback default instance m = _mapperConfig.getDefaultMapper(); } } return m; } /** * Overridable helper method used to allow handling of somewhat special * types for reading * * @since 2.2 */ protected boolean _isSpecialReadable(Class type) { return JsonParser.class == type; } /** * @since 2.4 */ protected IOException _createNoContentException() { Class cls = null; try { cls = Class.forName(CLASS_NAME_NO_CONTENT_EXCEPTION); Constructor ctor = cls.getDeclaredConstructor(String.class); if (ctor != null) { return (IOException) ctor.newInstance(NO_CONTENT_MESSAGE); } } catch (Exception e) { // no can do... } return new IOException(NO_CONTENT_MESSAGE); } /* /********************************************************** /* Private/sub-class helper methods /********************************************************** */ protected static boolean _containedIn(Class mainType, HashSet set) { if (set != null) { ClassKey key = new ClassKey(mainType); // First: type itself? if (set.contains(key)) return true; // Then supertypes (note: will not contain Object.class) for (Class cls : findSuperTypes(mainType, null)) { key.reset(cls); if (set.contains(key)) return true; } } return false; } protected Boolean _findCustomUntouchable(Class mainType) { if (_cfgCustomUntouchables != null) { ClassKey key = new ClassKey(mainType); // First: type itself? Boolean b = _cfgCustomUntouchables.get(key); if (b != null) { return b; } // Then supertypes (note: will not contain Object.class) for (Class cls : findSuperTypes(mainType, null)) { key.reset(cls); b = _cfgCustomUntouchables.get(key); if (b != null) { return b; } } } return null; } protected static List> findSuperTypes(Class cls, Class endBefore) { return findSuperTypes(cls, endBefore, new ArrayList>(8)); } protected static List> findSuperTypes(Class cls, Class endBefore, List> result) { _addSuperTypes(cls, endBefore, result, false); return result; } protected static void _addSuperTypes(Class cls, Class endBefore, Collection> result, boolean addClassItself) { if (cls == endBefore || cls == null || cls == Object.class) { return; } if (addClassItself) { if (result.contains(cls)) { // already added, no need to check supers return; } result.add(cls); } for (Class intCls : cls.getInterfaces()) { _addSuperTypes(intCls, endBefore, result, true); } _addSuperTypes(cls.getSuperclass(), endBefore, result, true); } @SuppressWarnings("unchecked") private final THIS _this() { return (THIS) this; } }