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}