package org.xbib.elasticsearch.index.analysis.combo; import java.io.BufferedReader; import java.io.CharArrayReader; import java.io.FilterReader; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.lang.ref.WeakReference; import java.util.WeakHashMap; /** * Duplicates {@link java.io.Reader}s in order to feed multiple consumers. * <p/> * This class registers multiple implementations, and tries to resolve which one to use, * looking at the actual class of the Reader to clone, and matching with the most bond * handled classes for each {@link ReaderCloner} implementation. * <p/> * By default, a few {@link java.io.Reader} implementations are handled, including the * most used inside Lucene ({@link java.io.StringReader}), and a default, fallback implementation * that merely reads all the available content, and creates a String out of it. * <p/> * Therefore you should understand the importance of having a proper implementation for * any optimizable {@link java.io.Reader}. For instance, {@link StringReaderCloner} gains access * to the underlying String in order to avoid copies. */ public class ReaderCloneFactory { /** * Interface for a utility class, able to unwrap a {@link java.io.Reader} * inside another {@link java.io.Reader}. * * @param <T> The base class handled. */ public static interface ReaderUnwrapper<T extends Reader> { /** * Unwraps a {@link java.io.Reader} from another, simplifying an eventual chain. */ public Reader unwrap(T originalReader) throws IllegalArgumentException; } /** * Interface for a utility class, able to clone the content of a {@link java.io.Reader}, * possibly in an optimized way (such as gaining access to a package private field, * or through reflection using {@link java.lang.reflect.Field#setAccessible(boolean)}). * * @param <T> The base class handled. */ public static interface ReaderCloner<T extends Reader> { /** * Initialize or reinitialize the cloner with the given reader. * The implementing class should have a default no arguments constructor. * <p/> * <P><B>Remark:</B> The given Reader is now controlled by this ReaderCloner, it may * be closed during a call to this method, or it may be returned * at first call to {@link #giveAClone()}. * * @see #giveAClone() */ public void init(T originalReader) throws IOException; /** * Returns a new {@link java.io.Reader}. * <P><B>Remark:</B> The returned Reader should be closed. * The original Reader, if not consumed by the {@link #init(java.io.Reader)} method, * should be returned at first call. Therefore it is important to * call this method at least once, or to be prepared to face possible * exceptions when closing the original Reader. */ public Reader giveAClone(); } /** * Map storing the mapping between a handled class and a handling class, for {@link ReaderCloner}s */ private static final WeakHashMap<Class<? extends Reader>, WeakReference<Class<? extends ReaderCloner>>> typeMap = new WeakHashMap<Class<? extends Reader>, WeakReference<Class<? extends ReaderCloner>>>(); /** * Map storing the mapping between a handled class and a handling instance, for {@link ReaderUnwrapper}s */ private static final WeakHashMap<Class<? extends Reader>, ReaderUnwrapper> unwrapperTypeMap = new WeakHashMap<Class<? extends Reader>, ReaderUnwrapper>(); /** * Add the association between a (handled) class and its handling {@link ReaderCloner}. * * @param handledClass The base class that is handled by clonerImplClass. * Using this parameter, you can further restrict the usage of a more generic cloner. * @param clonerImplClass The class of the associated cloner. * @param <T> The base handled class of the ReaderCloner. * @return The previously associated ReaderCloner for the handledClass. */ public static <T extends Reader> WeakReference<Class<? extends ReaderCloner>> bindCloner( Class<? extends T> handledClass, Class<? extends ReaderCloner<T>> clonerImplClass) { return typeMap.put(handledClass, new WeakReference<Class<? extends ReaderCloner>>(clonerImplClass)); } /** * Add the association between a (handled) class and its handling {@link ReaderUnwrapper} instance. * * @param handledClass The base class that is handled by clonerImplClass. * Using this parameter, you can further restrict the usage of a more generic cloner. * @param unwrapperImpl The instance of the associated unwrapper. * @param <T> The base handled class of the ReaderUnwrapper. * @return The previously associated ReaderUnwrapper instance for the handledClass. */ public static <T extends Reader> ReaderUnwrapper bindUnwrapper( Class<? extends T> handledClass, ReaderUnwrapper<T> unwrapperImpl) { return unwrapperTypeMap.put(handledClass, unwrapperImpl); } /** * Static initialization registering default associations */ static { // General purpose Reader handling bindCloner(Reader.class, ReaderClonerDefaultImpl.class); bindUnwrapper(BufferedReader.class, new BufferedReaderUnwrapper()); bindUnwrapper(FilterReader.class, new FilterReaderUnwrapper()); // Often used Java Readers bindCloner(StringReader.class, StringReaderCloner.class); // very, very used inside Lucene bindCloner(CharArrayReader.class, CharArrayReaderCloner.class); // Lucene specific handling ReusableStringReaderCloner.registerCloner(); } /** * (Expert) Returns the ReaderUnwrapper associated with the exact given class. * * @param forClass The handled class bond to the ReaderUnwrapper to return. * @param <T> The base handled class of the ReaderUnwrapper to return. * @return The bond ReaderUnwrapper, or null. */ @SuppressWarnings("unchecked") public static <T extends Reader> ReaderUnwrapper<T> getUnwrapperStrict(Class<? extends T> forClass) { return unwrapperTypeMap.get(forClass); } /** * Returns the ReaderCloner associated with the exact given class. * * @param forClass The handled class bond to the ReaderCloner to return. * @param <T> The base handled class of the ReaderCloner to return. * @return The bond ReaderCloner, or null. */ @SuppressWarnings("unchecked") public static <T extends Reader> ReaderCloner<T> getClonerStrict(Class<? extends T> forClass) { WeakReference<Class<? extends ReaderCloner>> refClonerClass = typeMap.get(forClass); if (refClonerClass != null) { Class<? extends ReaderCloner> clazz = refClonerClass.get(); if (clazz != null) { try { return (ReaderCloner<T>) clazz.newInstance(); } catch (Throwable ignored) { } } } return null; } /** * (Advanced) Returns an initialized ReaderCloner, associated with the exact class of the given Reader. * If the initialization fails, this function returns null. * * @param forReader The handled class bond to the ReaderCloner to return. * @param <T> The base handled class of the ReaderCloner to return. * @return The bond, initialized ReaderCloner, or null. */ @SuppressWarnings("unchecked") public static <T extends Reader> ReaderCloner<T> getClonerStrict(T forReader) { ReaderCloner<T> rtn = ReaderCloneFactory.<T>getClonerStrict((Class<? extends T>) forReader.getClass()); if (rtn != null) { try { rtn.init(forReader); } catch (Throwable fail) { return null; } } return rtn; } /** * (Advanced) Returns an initialized ReaderCloner, associated with the given base class, for the given Reader. * If the initialization fails, this function returns null. * <p/> * The function first tries to match the exact class of forReader, and initialize the ReaderCloner. * If no ReaderCloner or (tested second) ReaderUnwrapper matches, the resolution continues with the super class, * until the baseClass is reached, and tested. * <p/> * If this process is not successful, <code>null</code> is returned. * * @param baseClass The baseClass, above which the resolution will not try to continue with the super class. * @param forClass The class to start with, should be the class of forReader (but the latter can be null, hence this parameter) * @param forReader The Reader instance to return and initialize a ReaderCloner for. Can be null. * @param <T> The base handled class of the ReaderCloner to return * @param <S> The class of the given Reader to handle * @return An initialized ReaderCloner suitable for the givenReader, or null. */ @SuppressWarnings("unchecked") public static <T extends Reader, S extends T> ReaderCloner<T> getCloner(Class<T> baseClass, Class<S> forClass, final S forReader) { final Class<S> originalForClass = forClass; // Loop through each super class while (forClass != null) { // Try first a matching cloner ReaderCloner<T> cloner = ReaderCloneFactory.<T>getClonerStrict(forClass); if (cloner != null) { if (forReader != null) { try { cloner.init(forReader); } catch (Exception e) { //logger.debug("Error while initializing [{}]", e, cloner.getClass().getCanonicalName()); cloner = null; } } if (cloner != null) { return cloner; } } // Try then a matching unwrapper, for better suitability of the used cloner if (forReader != null) { ReaderUnwrapper<T> unwrapper = ReaderCloneFactory.<T>getUnwrapperStrict(forClass); if (unwrapper != null) { try { // Recursive resolution Reader unwrapped = unwrapper.unwrap(forReader); if (unwrapped != null) { return (ReaderCloner<T>) ReaderCloneFactory.getCloner(Reader.class, (Class<Reader>) unwrapped.getClass(), unwrapped); } } catch (Throwable ignore) { // in case of errors, simply continue the began process and forget about this failed attempt //logger.debug("Error while cloning [{}] with [{}]", ignore, unwrapper.getClass().getCanonicalName(), cloner.getClass().getCanonicalName()); } } } // Continue resolution with super class... Class clazz = forClass.getSuperclass(); // ... checking ancestry with the given base class if (baseClass.isAssignableFrom(clazz)) { forClass = clazz; } else { forClass = null; } } //if (forReader != null) //logger.debug("Could not find a suitable ReaderCloner for [{}]", forReader.getClass().getCanonicalName()); //else //logger.debug("Could not find a suitable ReaderCloner for class [{}]", originalForClass.getCanonicalName()); return null; } /** * Returns a ReaderCloner suitable for handling general <code>S</code>s instances (inheriting <code>T</code>, itself * inheriting {@link java.io.Reader}). * <p/> * Resolution starts on <code>forClass</code> (<code>S</code>), and does not go further than <code>baseClass</code>. * <p/> * Not all optimizations can be ran, like unwrapping and failing initialization fallback. * However, for standard cases, when performance is really critical, * using this function can reduce a possible resolution overhead * because ReaderCloner are reusable. * * @param baseClass The baseClass, above which the resolution will not try to continue with the super class. * @param forClass The class to start with, should be the class of forReader (but the latter can be null, hence this parameter) * @param <T> The base handled class of the ReaderCloner to return * @param <S> The class of the given Reader to handle * @return An uninitialized ReaderCloner suitable for any T Readers, or null. */ public static <T extends Reader, S extends T> ReaderCloner<T> getCloner(Class<T> baseClass, Class<S> forClass) { return ReaderCloneFactory.<T, S>getCloner(baseClass, forClass, null); } /** * Returns a ReaderCloner suitable for handling general <code>S</code>s instances (inheriting {@link java.io.Reader}). * <p/> * Calls <code>ReaderCloneFactory.<Reader,S>getCloner(Reader.class, forClass, (S)null)</code>. * <p/> * Not all optimizations can be ran, like unwrapping and failing initialization fallback. * However, for standard cases, when performance is really critical, * using this function can reduce a possible resolution overhead * because ReaderCloner are reusable. * * @param forClass The class to start with, should be the class of forReader (but the latter can be null, hence this parameter) * @param <S> The class of the given Reader to handle * @return An uninitialized ReaderCloner suitable for any <code>S</code>, or null. */ public static <S extends Reader> ReaderCloner<Reader> getCloner(Class<S> forClass) { return ReaderCloneFactory.<Reader, S>getCloner(Reader.class, forClass, null); } /** * Returns an initialized ReaderCloner, for the given Reader. * <p/> * Calls <code>ReaderCloneFactory.<Reader, S>getCloner(Reader.class, (Class<S>)forReader.getClass(), forReader)</code>. * If <code>forReader</code> is <code>null</code>, works as {@link ReaderCloneFactory#getGenericCloner()}. * * @param forReader The Reader instance to return and initialize a ReaderCloner for. Can be null. * @param <S> The class of the given Reader * @return An initialized ReaderCloner suitable for given Reader, or null. */ @SuppressWarnings("unchecked") public static <S extends Reader> ReaderCloner<Reader> getCloner(S forReader) { if (forReader != null) { return ReaderCloneFactory.<Reader, S>getCloner(Reader.class, (Class<S>) forReader.getClass(), forReader); } else { return ReaderCloneFactory.getGenericCloner(); } } /** * Returns a {@link ReaderCloner} suitable for any {@link java.io.Reader} instance. */ public static ReaderCloner<Reader> getGenericCloner() { return ReaderCloneFactory.getCloner(Reader.class, Reader.class, null); } }