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.solver;
21  
22  import org.apache.commons.lang3.builder.EqualsBuilder;
23  import org.apache.commons.lang3.builder.HashCodeBuilder;
24  import org.grouplens.grapht.CachePolicy;
25  import org.grouplens.grapht.reflect.Desire;
26  import org.grouplens.grapht.reflect.QualifierMatcher;
27  import org.grouplens.grapht.reflect.Satisfaction;
28  import org.grouplens.grapht.util.ClassProxy;
29  import org.grouplens.grapht.util.Preconditions;
30  import org.grouplens.grapht.util.Types;
31  
32  import javax.annotation.Nonnull;
33  import javax.annotation.Nullable;
34  import java.io.InvalidObjectException;
35  import java.io.ObjectInputStream;
36  import java.io.ObjectStreamException;
37  import java.io.Serializable;
38  import java.util.EnumSet;
39  
40  /**
41   * Foundational implementation of {@link BindRule}.
42   * 
43   * @author <a href="http://grouplens.org">GroupLens Research</a>
44   */
45  final class BindRuleImpl implements BindRule, Serializable {
46      private static final long serialVersionUID = -1L;
47  
48      private final Satisfaction satisfaction;
49      private final EnumSet<BindingFlag> flags;
50      
51      private final QualifierMatcher qualifier;
52      private final Class<?> depType;
53      private final Class<?> implType;
54      
55      private final CachePolicy policy;
56  
57      private transient volatile int hashCode;
58          
59      /**
60       * Create a bind rule that matches a desire when the desired type equals
61       * <tt>depType</tt> and the desire's qualifier matches <tt>qualifier</tt>
62       * .
63       * 
64       * @param depType The dependency type this bind rule matches
65       * @param satisfaction The Satisfaction used by applied desires
66       * @param policy The CachePolicy for nodes created by this bind rule
67       * @param qualifier The Qualifier the bind rule applies to
68       * @param flags The flags to apply to this bind rule and its results.
69       * @throws NullPointerException if arguments are null
70       */
71      public BindRuleImpl(@Nonnull Class<?> depType,
72                          @Nonnull Satisfaction satisfaction,
73                          @Nonnull CachePolicy policy,
74                          @Nonnull QualifierMatcher qualifier,
75                          EnumSet<BindingFlag> flags) {
76          Preconditions.notNull("dependency type", depType);
77          Preconditions.notNull("satisfaction", satisfaction);
78          Preconditions.notNull("policy", policy);
79          Preconditions.notNull("qualifier matcher", qualifier);
80          
81          this.qualifier = qualifier;
82          this.satisfaction = satisfaction;
83          this.implType = satisfaction.getErasedType();
84          this.policy = policy;
85          this.depType = Types.box(depType);
86          this.flags = flags.clone();
87          
88          // verify that the satisfaction produces proper types
89          Preconditions.isAssignable(this.depType, this.implType);
90      }
91      
92      /**
93       * As the other constructor, but this is used for type to type bindings
94       * where the implementation type is not yet instantiable, so there is no
95       * satisfaction for the applied desires.
96       * 
97       * @param depType The dependency type this bind rule matches
98       * @param implType The implementation type that is bound
99       * @param policy The CachePolicy for nodes created by this bind rule
100      * @param qualifier The Qualifier the bind rule applies to
101      * @param flags The flags to apply to this bind rule and its results.
102      * @throws NullPointerException if arguments are null
103      */
104     public BindRuleImpl(@Nonnull Class<?> depType,
105                         @Nonnull Class<?> implType,
106                         @Nonnull CachePolicy policy,
107                         @Nonnull QualifierMatcher qualifier,
108                         EnumSet<BindingFlag> flags) {
109         Preconditions.notNull("dependency type", depType);
110         Preconditions.notNull("implementation type", implType);
111         Preconditions.notNull("policy", policy);
112         Preconditions.notNull("qualifier matcher", qualifier);
113         
114         this.qualifier = qualifier;
115         this.satisfaction = null;
116         this.implType = Types.box(implType);
117         this.policy = policy;
118         this.depType = Types.box(depType);
119         this.flags = flags.clone();
120         
121         // verify that implType extends depType
122         Preconditions.isAssignable(this.depType, this.implType);
123     }
124 
125     /**
126      * Get the rule's qualifier matcher.
127      *
128      * @return The annotation {@link QualifierMatcher} matched by this bind rule.
129      */
130     public QualifierMatcher getQualifierMatcher() {
131         return qualifier;
132     }
133 
134     @Override
135     public CachePolicy getCachePolicy() {
136         return policy;
137     }
138     
139     @Override
140     public Desire apply(Desire desire) {
141         // TODO Separate bind rules into different classes based on sat vs. type targets
142         if (satisfaction != null) {
143             return desire.restrict(satisfaction);
144         } else {
145             return desire.restrict(implType);
146         }
147     }
148 
149     @Override
150     public EnumSet<BindingFlag> getFlags() {
151         return flags;
152     }
153 
154     @Override
155     public boolean isTerminal() {
156         return flags.contains(BindingFlag.TERMINAL);
157     }
158     
159     @Override
160     public boolean matches(Desire desire) {
161         // bind rules match type by equality
162         if (desire.getDesiredType().equals(depType)) {
163             // if the type is equal, then rely on the qualifier matcher
164             return qualifier.matches(desire.getInjectionPoint().getQualifier());
165         }
166         
167         // the type and {@link Qualifier}s are not a match, so return false
168         return false;
169     }
170 
171     @Override
172     public BindRuleBuilder newCopyBuilder() {
173         BindRuleBuilder bld = new BindRuleBuilder();
174         bld.setDependencyType(depType)
175            .setQualifierMatcher(qualifier)
176            .setCachePolicy(policy)
177            .setFlags(flags);
178         if (satisfaction != null) {
179             bld.setSatisfaction(satisfaction);
180         } else {
181             bld.setImplementation(implType);
182         }
183         return bld;
184     }
185 
186     @Override
187     public int compareTo(BindRule other) {
188         if (other instanceof BindRuleImpl) {
189             return qualifier.compareTo(((BindRuleImpl) other).qualifier);
190         } else {
191             throw new IllegalArgumentException("incompatible bind rule");
192         }
193     }
194     
195     @Override
196     public boolean equals(Object o) {
197         if (o == this) {
198             return true;
199         } else if (o instanceof BindRuleImpl) {
200             EqualsBuilder eq = new EqualsBuilder();
201             BindRuleImpl or = (BindRuleImpl) o;
202             return eq.append(depType, or.depType)
203                     .append(implType, or.implType)
204                     .append(flags, or.flags)
205                     .append(qualifier, or.qualifier)
206                     .append(policy, or.policy)
207                     .append(satisfaction, or.satisfaction)
208                     .isEquals();
209         } else {
210             return false;
211         }
212     }
213     
214     @Override
215     public int hashCode() {
216         if (hashCode == 0) {
217             HashCodeBuilder hcb = new HashCodeBuilder();
218             hashCode = hcb.append(flags)
219                           .append(depType)
220                           .append(implType)
221                           .append(qualifier)
222                           .append(policy)
223                           .append(satisfaction)
224                           .toHashCode();
225         }
226         return hashCode;
227     }
228     
229     @Override
230     public String toString() {
231         String i = (satisfaction == null ? implType.getSimpleName() : satisfaction.toString());
232         return "Bind(" + qualifier + ":" + depType.getSimpleName() + " -> " + i + ", " + flags + ")";
233     }
234 
235     private Object writeReplace() {
236         return new SerialProxy(satisfaction, flags, qualifier,
237                                depType, implType, policy);
238     }
239 
240     private void readObject(ObjectInputStream stream) throws ObjectStreamException {
241         throw new InvalidObjectException("must use serialization proxy");
242     }
243 
244     /**
245      * Serialization proxy class.
246      */
247     private static class SerialProxy implements Serializable {
248         private static final long serialVersionUID = 2L;
249 
250         private final ClassProxy depType;
251         private final QualifierMatcher qualifier;
252         private final ClassProxy implType;
253 
254         @Nullable
255         private final Satisfaction satisfaction;
256         private final EnumSet<BindingFlag> flags;
257         private final CachePolicy cachePolicy;
258 
259         private SerialProxy(@Nullable Satisfaction sat, EnumSet<BindingFlag> flags, QualifierMatcher qmatch,
260                             Class<?> stype, Class<?> itype, CachePolicy policy) {
261             satisfaction = sat;
262             this.flags = flags;
263             qualifier = qmatch;
264             depType = ClassProxy.of(stype);
265             implType = ClassProxy.of(itype);
266             cachePolicy = policy;
267         }
268 
269         private Object readResolve() throws ObjectStreamException {
270             try {
271                 if (satisfaction == null) {
272                     return new BindRuleImpl(depType.resolve(), implType.resolve(),
273                                             cachePolicy, qualifier, flags);
274                 } else {
275                     return new BindRuleImpl(depType.resolve(), satisfaction,
276                                             cachePolicy, qualifier, flags);
277                 }
278             } catch (ClassNotFoundException e) {
279                 InvalidObjectException ex = new InvalidObjectException("cannot resolve type");
280                 ex.initCause(e);
281                 throw ex;
282             }
283         }
284     }
285 }