View Javadoc

1   /*
2    * Grapht, an open source dependency injector.
3    * Copyright 2014-2015 various contributors (see CONTRIBUTORS.txt)
4    * Copyright 2010-2014 Regents of the University of Minnesota
5    *
6    * This program is free software; you can redistribute it and/or modify
7    * it under the terms of the GNU Lesser General Public License as
8    * published by the Free Software Foundation; either version 2.1 of the
9    * License, or (at your option) any later version.
10   *
11   * This program is distributed in the hope that it will be useful, but WITHOUT
12   * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13   * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14   * details.
15   *
16   * You should have received a copy of the GNU General Public License along with
17   * this program; if not, write to the Free Software Foundation, Inc., 51
18   * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19   */
20  package org.grouplens.grapht.reflect.internal;
21  
22  import com.google.common.collect.Lists;
23  import org.grouplens.grapht.InvalidBindingException;
24  import org.grouplens.grapht.reflect.Desire;
25  import org.grouplens.grapht.reflect.InjectionPoint;
26  import org.grouplens.grapht.reflect.Satisfaction;
27  import org.grouplens.grapht.util.ClassProxy;
28  import org.grouplens.grapht.util.Preconditions;
29  import org.grouplens.grapht.util.Types;
30  
31  import javax.inject.Inject;
32  import java.io.InvalidObjectException;
33  import java.io.ObjectInputStream;
34  import java.io.ObjectStreamException;
35  import java.io.Serializable;
36  import java.lang.reflect.*;
37  import java.util.*;
38  
39  /**
40   * ReflectionDesire is an implementation of desire that contains all necessary
41   * implementation to represent a desire, except that the point of injection is
42   * abstracted by an {@link InjectionPoint}.
43   * 
44   * @author <a href="http://grouplens.org">GroupLens Research</a>
45   */
46  public class ReflectionDesire implements Desire, Serializable {
47      private static final long serialVersionUID = -1L;
48  
49      /**
50       * Return a list of desires that must satisfied in order to instantiate the
51       * given type.
52       *
53       * @param type The class type whose dependencies will be queried
54       * @return The dependency desires for the given type
55       * @throws NullPointerException if the type is null
56       */
57      public static List<Desire> getDesires(Class<?> type) {
58          List<Desire> desires = Lists.newArrayList();
59  
60          boolean ctorFound = false;
61          for (Constructor<?> ctor: type.getDeclaredConstructors()) {
62              if (ctor.getAnnotation(Inject.class) != null) {
63                  if (!ctorFound) {
64                      ctorFound = true;
65                      for (int i = 0; i < ctor.getParameterTypes().length; i++) {
66                          desires.add(new ReflectionDesire(new ConstructorParameterInjectionPoint(ctor, i)));
67                      }
68                  } else {
69                      // at the moment there can only be one injectable constructor
70                      throw new InvalidBindingException(type, "More than one constructor with @Inject is not allowed");
71                  }
72              }
73          }
74          
75          // JSR 330 mandates that super class methods are injected first, so we
76          // collect method injection points into a separate list and then reverse
77          // it to get the ordering correct.
78          List<Desire> groupDesires = Lists.newArrayList();
79          
80          // Must also keep track of methods overridden in the subtypes.
81          Set<Signature> visitedMethods = new HashSet<Signature>();
82          while(type != null) {
83              for (Method m: type.getDeclaredMethods()) {
84                  Signature s = new Signature(m);
85                  if (!visitedMethods.contains(s) && m.getAnnotation(Inject.class) != null
86                      && !Modifier.isStatic(m.getModifiers())) {
87                      // have not seen this signature, and its an injection point
88                      if (m.getParameterTypes().length > 0) {
89                          for (int i = 0; i < m.getParameterTypes().length; i++) {
90                              groupDesires.add(new ReflectionDesire(new SetterInjectionPoint(m, i)));
91                          }
92                      } else {
93                          // hack to invoke no-argument injectable methods required by JSR 330
94                          groupDesires.add(new ReflectionDesire(new NoArgumentInjectionPoint(m)));
95                      }
96                  }
97                  // always add signature, because a subclass without @Inject
98                  // overrides any @Inject on the superclass's method declaration
99                  visitedMethods.add(s);
100             }
101             for (Field f: type.getDeclaredFields()) {
102                 if (f.getAnnotation(Inject.class) != null && !Modifier.isStatic(f.getModifiers())) {
103                     // have not seen this field
104                     groupDesires.add(new ReflectionDesire(new FieldInjectionPoint(f)));
105                 }
106             }
107             
108             type = type.getSuperclass();
109         }
110         
111         // after reversing this list, fields will be injected 
112         // before methods as required
113         Collections.reverse(groupDesires);
114         desires.addAll(groupDesires);
115         
116         return Collections.unmodifiableList(desires);
117     }
118     
119     private final transient Class<?> desiredType;
120     private final transient InjectionPoint injectPoint;
121     private final transient Satisfaction satisfaction;
122 
123     /**
124      * Create a ReflectionDesire that immediately wraps the given
125      * InjectionPoint. The desired type equals the type declared by the
126      * injection point. The created desire will have a satisfaction if the
127      * injection point's type is satisfiable.
128      * 
129      * @param injectPoint The injection point to wrap
130      * @throws NullPointerException if injectPoint is null
131      */
132     public ReflectionDesire(InjectionPoint injectPoint) {
133         this(injectPoint.getErasedType(), injectPoint, null);
134     }
135 
136     /**
137      * Create a ReflectionDesire that represents the dependency for
138      * <tt>desiredType</tt> that will be injected into the given InjectionPoint.
139      * The optional satisfaction will satisfy this desire. If null is provided,
140      * and the desired type instantiable, a ClassSatisfaction is created.
141      * 
142      * @param desiredType The desired type of the dependency
143      * @param injectPoint The injection point of the desire
144      * @param satisfaction The satisfaction satisfying this desire, if there is
145      *            one
146      * @throws NullPointerException if desiredType, injectPoint, or dfltSource is null
147      * @throws IllegalArgumentException if desiredType is not assignable to the
148      *             type of the injection point, or if the satisfaction's type is
149      *             not assignable to the desired type
150      */
151     public ReflectionDesire(Class<?> desiredType, InjectionPoint injectPoint,
152                             Satisfaction satisfaction) {
153         Preconditions.notNull("desired type", desiredType);
154         Preconditions.notNull("injection point", injectPoint);
155 
156         desiredType = Types.box(desiredType);
157         Preconditions.isAssignable(injectPoint.getErasedType(), desiredType);
158         if (satisfaction != null) {
159             Preconditions.isAssignable(desiredType, satisfaction.getErasedType());
160         }
161 
162         // try and find a satisfaction
163         if (satisfaction == null) {
164             if (Types.shouldBeInstantiable(desiredType)) {
165                 if (Types.isInstantiable(desiredType)) {
166                     satisfaction = new ClassSatisfaction(desiredType);
167                 } // else don't satisfy this, even if the injection point is null
168             } else if (injectPoint.isNullable()) {
169                 // we only default to null if the injection point depended on
170                 // an interface. if they ask for a concrete type, it's a bug
171                 // for that type not to be injectable
172                 satisfaction = new NullSatisfaction(desiredType);
173             }
174         }
175 
176         this.desiredType = desiredType;
177         this.injectPoint = injectPoint;
178         this.satisfaction = satisfaction;
179     }
180 
181     @Override
182     public Class<?> getDesiredType() {
183         return desiredType;
184     }
185 
186     @Override
187     public InjectionPoint getInjectionPoint() {
188         return injectPoint;
189     }
190 
191     @Override
192     public boolean isInstantiable() {
193         return satisfaction != null;
194     }
195 
196     @Override
197     public Satisfaction getSatisfaction() {
198         return satisfaction;
199     }
200     
201     @Override
202     public Desire restrict(Class<?> type) {
203         return new ReflectionDesire(type, injectPoint, null);
204     }
205     
206     @Override
207     public Desire restrict(Satisfaction satis) {
208         return new ReflectionDesire(satis.getErasedType(), injectPoint, satis);
209     }
210 
211     @Override
212     public boolean equals(Object o) {
213         if (!(o instanceof ReflectionDesire)) {
214             return false;
215         }
216         ReflectionDesire r = (ReflectionDesire) o;
217         return (r.desiredType.equals(desiredType) && 
218                 r.injectPoint.equals(injectPoint) && 
219                 (r.satisfaction == null ? satisfaction == null : r.satisfaction.equals(satisfaction)));
220     }
221 
222     @Override
223     public int hashCode() {
224         return desiredType.hashCode() ^ injectPoint.hashCode() ^ (satisfaction == null ? 0 : satisfaction.hashCode());
225     }
226 
227     @Override
228     public String toString() {
229         return "Desire(" + desiredType.getSimpleName() + ", " + injectPoint + ")";
230     }
231 
232     private Object writeReplace() {
233         return new SerialProxy(desiredType, injectPoint, satisfaction);
234     }
235 
236     private void readObject(ObjectInputStream stream) throws ObjectStreamException {
237         throw new InvalidObjectException("must use serialization proxy");
238     }
239 
240     private static class SerialProxy implements Serializable {
241         private static final long serialVersionUID = 1L;
242 
243         private final InjectionPoint injectionPoint;
244         private final ClassProxy desiredType;
245         private final Satisfaction satisfaction;
246 
247         public SerialProxy(Class<?> type, InjectionPoint ip, Satisfaction sat) {
248             injectionPoint = ip;
249             desiredType = ClassProxy.of(type);
250             satisfaction = sat;
251         }
252 
253         @SuppressWarnings("unchecked")
254         private Object readResolve() throws ObjectStreamException {
255             try {
256                 return new ReflectionDesire(desiredType.resolve(),
257                                             injectionPoint,
258                                             satisfaction);
259             } catch (ClassNotFoundException e) {
260                 InvalidObjectException ex = new InvalidObjectException("cannot resolve " + desiredType);
261                 ex.initCause(e);
262                 throw ex;
263             } catch (InvalidBindingException e) {
264                 InvalidObjectException ex = new InvalidObjectException("invalid binding");
265                 ex.initCause(e);
266                 throw ex;
267             }
268         }
269     }
270     
271     /*
272      * Internal class to track a methods signature. Java's default reflection
273      * doesn't give us a convenient way to record just this information.
274      *
275      * FIXME Document why we need this class more clearly
276      */
277     public static class Signature {
278         private final String name;
279         private final Type[] args;
280         
281         public Signature(Method m) {
282             // FIXME Make it clearer what this code is supposed to do
283             int mods = m.getModifiers();
284             if (Modifier.isPublic(mods) || Modifier.isProtected(mods)) {
285                 // method overrides depends solely on method name
286                 name = m.getName();
287             } else if (Modifier.isPrivate(mods)) {
288                 // method overrides depend on method name and class name
289                 name = m.getName() + m.getDeclaringClass().getCanonicalName();
290             } else {
291                 // method overrides depend on method name and package,
292                 // since it is package-private
293                 Package pkg = m.getDeclaringClass().getPackage();
294                 if (pkg != null) {
295                     name = m.getName() + pkg.getName();
296                 } else {
297                     name = m.getName();
298                 }
299             }
300             args = m.getGenericParameterTypes();
301         }
302         
303         @Override
304         public boolean equals(Object o) {
305             if (!(o instanceof Signature)) {
306                 return false;
307             }
308             Signature s = (Signature) o;
309             return s.name.equals(name) && Arrays.equals(args, s.args);
310         }
311         
312         @Override
313         public int hashCode() {
314             return (name.hashCode() ^ Arrays.hashCode(args));
315         }
316     }
317 }