001package com.avaje.ebean;
002
003import java.lang.reflect.Field;
004import java.lang.reflect.Modifier;
005
006/**
007 * Used to replace a "Finder" that is located as a static field (typically on an Model entity bean).
008 * <p/>
009 * This uses reflection to get/set the finder implementation with the ability to replace the original implementation
010 * with the test double and restoring the original.
011 */
012public class WithStaticFinder<T> {
013
014  Class<T> beanType;
015
016  String fieldName;
017
018  Field field;
019
020  Object original;
021
022  Object testDouble;
023
024  /**
025   * Construct with a given bean type.
026   */
027  public WithStaticFinder(Class<T> beanType) {
028    this.beanType = beanType;
029  }
030
031  /**
032   * Construct with a given bean type.
033   */
034  public WithStaticFinder(Class<T> beanType, String fieldName) {
035    this.beanType = beanType;
036    this.fieldName = fieldName;
037  }
038
039  /**
040   * Set the test double instance to use after useTestDouble() has been called.
041   * <p/>
042   * Note that the test double instance is not set until <code>useTestDouble()</code> is called.
043   */
044  public WithStaticFinder as(Object testDouble) throws FinderFieldNotFoundException {
045
046    try {
047      this.testDouble = testDouble;
048
049      this.field = findField();
050      this.field.setAccessible(true);
051      try {
052        Field modifiersField = Field.class.getDeclaredField("modifiers");
053        modifiersField.setAccessible(true);
054        modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);
055      } catch (NoSuchFieldException e) {
056        throw new RuntimeException(e);
057      }
058
059      this.original = field.get(null);
060      return this;
061
062    } catch (IllegalAccessException e) {
063      throw new RuntimeException(e);
064    }
065  }
066
067
068  /**
069   * Set the test double to the field using reflection.
070   * <p/>
071   * After this the test double will be used by calling code.
072   */
073  public void useTestDouble() {
074    try {
075      this.field.set(null, testDouble);
076    } catch (IllegalAccessException e) {
077      throw new FinderIllegalAccessException(e);
078    }
079  }
080
081  /**
082   * Restore the original implementation using reflection.
083   */
084  public void restoreOriginal() {
085
086    try {
087      this.field.set(null, original);
088    } catch (IllegalAccessException e) {
089      throw new FinderIllegalAccessException(e);
090    }
091  }
092
093  /**
094   * Find and return the "find" field.
095   */
096  protected Field findField() throws FinderFieldNotFoundException {
097
098    try {
099      if (fieldName != null) {
100        return beanType.getField(fieldName);
101      }
102
103      try {
104        return beanType.getField("find");
105
106      } catch (NoSuchFieldException e) {
107        return beanType.getField("FIND");
108      }
109    } catch (NoSuchFieldException e) {
110      throw new FinderFieldNotFoundException(e);
111    }
112  }
113
114  /**
115   * Set the given test double onto the given class returning the WithStaticFinder.
116   * <p>
117   * Use restore to put the original finder back onto the class.
118   * </p>
119   *
120   * <pre>{@code
121   *
122   *   // Replace the finder with TDCustFinder
123   *   WithStaticFinder with = WithStaticFinder.use(Customer.class, new TDCustFinder());
124   *   try {
125   *     // perform some test
126   *     Customer jim = Customer.find.byName("jim");
127   *
128   *   } finally {
129   *     // restore the original finder
130   *     with.restoreOriginal();
131   *   }
132   *
133   * }</pre>
134   */
135  public static <U> WithStaticFinder<U> use(Class<U> cls, Object testFinder) {
136
137    WithStaticFinder<U> with = new WithStaticFinder<>(cls);
138    with.as(testFinder);
139    with.useTestDouble();
140    return with;
141  }
142}