View Javadoc

1   /**
2    * Project MI+
3    *
4    * Copyright (c) 2013, Syed Muhammad Humayun - smhumayun@gmail.com
5    *
6    * This project includes software developed by Syed Muhammad Humayun - smhumayun@gmail.com
7    * http://www.smhumayun.com
8    *
9    * Licensed under the Apache License, Version 2.0 (the
10   * "License"); you may not use this file except in compliance
11   * with the License. You may obtain a copy of the License at:
12   *
13   * http://www.apache.org/licenses/LICENSE-2.0
14   *
15   * Unless required by applicable law or agreed to in writing,
16   * software distributed under the License is distributed on
17   * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18   * KIND, either express or implied. See the License for the
19   * specific language governing permissions and limitations
20   * under the License.
21   */
22  package com.smhumayun.mi_plus;
23  
24  import com.smhumayun.mi_plus.util.Pair;
25  import com.smhumayun.mi_plus.util.Utils;
26  
27  import java.lang.reflect.InvocationHandler;
28  import java.lang.reflect.Method;
29  import java.lang.reflect.Proxy;
30  import java.util.Arrays;
31  import java.util.LinkedHashMap;
32  import java.util.Random;
33  import java.util.logging.Logger;
34  
35  /**
36   * Provides core support for emulation of multiple inheritance behavior using Object Composition and Method Delegation
37   *
38   * @author smhumayun
39   * @since 1.0
40   */
41  final class MIContainerSupport implements InvocationHandler {
42  
43      /**
44       * MI Container class - the class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
45       */
46      private Class miContainerClass;
47  
48      /**
49       * Holds composed objects
50       */
51      private LinkedHashMap<Class, Object> composedObjects = new LinkedHashMap<Class, Object>();
52  
53      /**
54       * Method resolver
55       * @see {@link com.smhumayun.mi_plus.MIMethodResolver}
56       */
57      private MIMethodResolver miMethodResolver;
58  
59      /**
60       * LOGGER {@link java.util.logging.Logger}
61       */
62      private Logger logger = Logger.getLogger(MIContainerSupport.class.getName());
63  
64      /**
65       * Utils
66       */
67      private Utils utils = new Utils();
68  
69      /**
70       * Seed for hashCode
71       */
72      private int seed;
73  
74      /**
75       * Random number generator for seed
76       */
77      private static final Random RANDOM = new Random(31);
78  
79      /**
80       * Constructor will initialize this object with given <code>miContainerClass</code>
81       * , <code>miMethodResolver</code> and <code>logger</code> objects
82       *
83       * @param miContainerClass MI Container class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
84       * @param miMethodResolver method resolver
85       * @throws com.smhumayun.mi_plus.MIException {@link com.smhumayun.mi_plus.MIException}
86       */
87      MIContainerSupport(Class miContainerClass, MIMethodResolver miMethodResolver) throws MIException {
88          setMiContainerClass(miContainerClass);
89          setMiMethodResolver(miMethodResolver);
90          this.seed = RANDOM.nextInt() + RANDOM.nextInt();
91      }
92  
93      /**
94       * Compose <code>objectToCompose</code>
95       * NOTE: <code>objectToCompose</code> will be validated via {@link #validateAndCompose(Object)}
96       *
97       * @param objectToCompose object to compose
98       * @throws MIException {@link MIException}
99       */
100     void compose (Object objectToCompose) throws MIException {
101         //validate and thereafter compose
102         validateAndCompose(objectToCompose);
103     }
104 
105     /**
106      * Validate <code>objectToCompose</code>
107      *
108      * @param objectToCompose object to validate for composition
109      * @throws MIException {@link MIException}
110      */
111     private void validateAndCompose (Object objectToCompose) throws MIException {
112         //check if the provided object to compose is not null
113         if(objectToCompose == null)
114             //if null throw exception
115             throw new MIException("Composed object must not be null");
116         //check if the same type of object has already been composed
117         if(composedObjects.containsKey(objectToCompose.getClass()))
118             //throw exception
119             throw new MIException("Already inherited from " + objectToCompose.getClass().getName());
120         logger.fine("Composing object of type " + objectToCompose.getClass().getName());
121         //add the object to the list of composed objects. Why list? because order of composition matters
122         //noinspection unchecked
123         composedObjects.put(objectToCompose.getClass(), objectToCompose);
124     }
125 
126     /**
127      * Please refer to {@link InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])}
128      *
129      * @param    proxy the proxy instance that the method was invoked on
130      * @param    method the <code>Method</code> instance corresponding to
131      * the interface method invoked on the proxy instance.
132      * @param    args an array of objects containing the values of the
133      * arguments passed in the method invocation on the proxy instance,
134      * or <code>null</code> if interface method takes no arguments.
135      * @return the value to return from the method invocation on the
136      * proxy instance.
137      * @see    {@link java.lang.reflect.InvocationHandler#invoke(java.lang.Object, java.lang.reflect.Method, java.lang.Object[])}
138      */
139     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
140         logger.info("invoke - method: " + method.toGenericString() + " - args[]: " + Arrays.toString(utils.getClasses(args)));
141         if(method.getName().equals("equals") && args != null && args.length == 1)
142         {
143             logger.finer("proxy.getClass().getName() = " + proxy.getClass().getName());
144             logger.finer("args[0].getClass().getName() = " + args[0].getClass().getName());
145             return proxy.getClass().getName().equals(args[0].getClass().getName()) && this.equals(Proxy.getInvocationHandler(args[0]));
146         }
147         if(method.getName().equals("hashCode") && (args == null || args.length == 0))
148             return this.hashCode();
149         //resolve target method using selected strategy
150         Pair<Object, Method> target = getMiMethodResolver()
151                 .resolve(miContainerClass, composedObjects, method, args);
152         //if target object found with the best method match
153         if(target.getLeft() != null && target.getRight() != null)
154             //invoke method on that target object and return the result to the caller
155             return target.getRight().invoke(target.getLeft(), args);
156         //in any other case, throw exception
157         throw new UnsupportedOperationException("Method not found - " + method.toGenericString());
158     }
159 
160     /**
161      * Indicates whether some other object is "equal to" this one.
162      *
163      * @param obj the reference object with which to compare.
164      * @return <code>true</code> if this object is the same as the obj
165      *         argument; <code>false</code> otherwise.
166      * @see #hashCode()
167      */
168     @Override
169     public boolean equals(Object obj) {
170         if(obj == null)
171             return false;
172         if(obj == this)
173             return true;
174         if(obj.getClass() != getClass())
175             return false;
176         MIContainerSupport other = (MIContainerSupport) obj;
177         return this.seed == other.seed
178                 && this.getMiContainerClass().equals(other.getMiContainerClass())
179                 && this.getComposedObjects().equals(other.getComposedObjects());
180     }
181 
182     /**
183      * Returns a hash code value for the object. This method is
184      * supported for the benefit of hashtables such as those provided by
185      * <code>java.util.Hashtable</code>.
186      *
187      * @return  a hash code value for this object.
188      * @see     java.lang.Object#equals(java.lang.Object)
189      * @see     java.util.Hashtable
190      */
191     @Override
192     public int hashCode() {
193         System.out.println("this.seed = " + this.seed);
194         int result = 3 * this.seed;
195         System.out.println("getMiContainerClass().hashCode() = " + getMiContainerClass().hashCode());
196         result = result * 5 + getMiContainerClass().hashCode();
197         System.out.println("getComposedObjects().hashCode() = " + getComposedObjects().hashCode());
198         result = result * 7 + getComposedObjects().hashCode();
199         System.out.println("result = " + result);
200         return result;
201     }
202 
203     /**
204      * Get list of composed objects
205      *
206      * @return list of composed objects
207      */
208     LinkedHashMap<Class, Object> getComposedObjects() {
209         return composedObjects;
210     }
211 
212     /**
213      * Set list of composed objects
214      *
215      * @param composedObjects list of composed objects
216      * @throws MIException {@link MIException}
217      */
218     void setComposedObjects(LinkedHashMap<Class, Object> composedObjects) throws MIException {
219         if(composedObjects == null)
220             throw new MIException("Composed objects should not be null");
221         for(Object composedObject : composedObjects.values())
222             validateAndCompose(composedObject);
223     }
224 
225     /**
226      * Get method resolver
227      *
228      * @return method resolver
229      * @see {@link MIMethodResolver}
230      */
231     MIMethodResolver getMiMethodResolver() {
232         return miMethodResolver;
233     }
234 
235     /**
236      * Set method resolver
237      *
238      * @param miMethodResolver method resolver
239      * @see {@link MIMethodResolver}
240      * @throws MIException {@link MIException}
241      */
242     void setMiMethodResolver(MIMethodResolver miMethodResolver) throws MIException {
243         if(miMethodResolver == null)
244             throw new MIException("miMethodResolver is NULL");
245         this.miMethodResolver = miMethodResolver;
246     }
247 
248     /**
249      * Get MI Container class - the class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
250      *
251      * @return MI Container class - the class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
252      */
253     Class getMiContainerClass() {
254         return miContainerClass;
255     }
256 
257     /**
258      * Set MI Container class - the class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
259      *
260      * @param miContainerClass MI Container class - the class which has the {@link com.smhumayun.mi_plus.MISupport} annotation
261      */
262     void setMiContainerClass(Class miContainerClass) throws MIException {
263         if(miContainerClass == null)
264             throw new MIException("miContainerClass is NULL");
265         this.miContainerClass = miContainerClass;
266     }
267 
268 }