View Javadoc

1   /*
2    * Entreri, an entity-component framework in Java
3    *
4    * Copyright (c) 2013, Michael Ludwig
5    * All rights reserved.
6    *
7    * Redistribution and use in source and binary forms, with or without modification,
8    * are permitted provided that the following conditions are met:
9    *
10   *     Redistributions of source code must retain the above copyright notice,
11   *         this list of conditions and the following disclaimer.
12   *     Redistributions in binary form must reproduce the above copyright notice,
13   *         this list of conditions and the following disclaimer in the
14   *         documentation and/or other materials provided with the distribution.
15   *
16   * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17   * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18   * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19   * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
20   * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22   * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23   * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24   * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25   * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26   */
27  package com.lhkbob.entreri.impl;
28  
29  import com.lhkbob.entreri.Component;
30  import com.lhkbob.entreri.property.ObjectProperty;
31  
32  import javax.lang.model.SourceVersion;
33  import java.nio.ByteBuffer;
34  import java.nio.charset.Charset;
35  import java.security.MessageDigest;
36  import java.security.NoSuchAlgorithmException;
37  import java.text.DateFormat;
38  import java.text.SimpleDateFormat;
39  import java.util.*;
40  
41  /**
42   * ComponentFactoryProvider provides Factory instances that can create instances of
43   * components on demand.  The provider encapsulates the strategy used to create or
44   * generate the component implementations. The two current approaches are to generate the
45   * source at build time and look them up using reflection, and to use Janino to
46   * dynamically generate and compile classes, although a Janino factory is not provided
47   * currently because the annotation processor works sufficiently well.
48   *
49   * @author Michael Ludwig
50   */
51  public abstract class ComponentFactoryProvider {
52      /**
53       * Factory is a factory specification for creating proxy implementations of a
54       * particular component interface. Additionally, to be compatible with the underlying
55       * details of Entreri, all implementations must be AbstractComponents in addition to
56       * implementing whatever required component type.
57       *
58       * @param <T> The component interface all created interfaces implement from this
59       *            factory
60       */
61      public static interface Factory<T extends Component> {
62          /**
63           * Create a new instance that will be managed by the given ComponentRepository.
64           * Depending on how it's used by the repository, it may or may not be flyweight.
65           * The created component must be an AbstractComponent, and be cast-able to an
66           * instance of T.
67           *
68           * @param forRepository The repository using the instance
69           *
70           * @return A new component instance
71           */
72          public AbstractComponent<T> newInstance(ComponentRepository<T> forRepository);
73  
74          /**
75           * Get the property specification used by this factory. The list will be immutable
76           * and not change during runtime.  The ordering must be consistent and ordered by
77           * PropertySpecification's natural ordering.
78           *
79           * @return The property specification used by the factory
80           */
81          public ComponentSpecification getSpecification();
82      }
83  
84      /**
85       * Create or lookup the factory capable of creating the given component type. In many
86       * cases the factory will have a comparatively expensive creation time and should be
87       * cached and reused to mitigate this cost. Actual instance creation will likely be as
88       * cheap as a reflection-based constructor invocation.
89       * <p/>
90       * This method is thread-safe and will guarantee there is at most a single factory
91       * instance for each type. However, the {@link CachingDelegatingFactoryProvider}
92       * implements this logic so that actual factory providers can have a simpler
93       * implementation (where it is acceptable to allocate a new factory when their
94       * getFactory() method is called).
95       *
96       * @param componentType The component type of the returned factory
97       * @param <T>           The component type
98       *
99       * @return The unique factory for the given component type from this provider
100      */
101     public abstract <T extends Component> Factory<T> getFactory(Class<T> componentType);
102 
103     /**
104      * Get the singleton thread-safe factory provider that can be used to create factories
105      * for each component type.  This is intended for internal use only.
106      *
107      * @return The singleton provider
108      */
109     public static ComponentFactoryProvider getInstance() {
110         return INSTANCE;
111     }
112 
113     private static final ComponentFactoryProvider INSTANCE = new CachingDelegatingFactoryProvider();
114 
115     /**
116      * Get the unique implementation name that should be used when generating source files
117      * or looking for an existing proxy class that implements the given component type. If
118      * <var>includePackage</var> is true, the returned string will include the package
119      * name to create a valid, absolute type name.
120      *
121      * @param spec           The component specification
122      * @param includePackage True if the package should be included
123      *
124      * @return The class name that corresponds to the generated proxy implmentation for
125      *         the given type
126      */
127     public static String getImplementationClassName(ComponentSpecification spec,
128                                                     boolean includePackage) {
129         // first get the simple name, concatenating all types in the hierarchy
130         // (e.g. if its an inner class the name is Foo.Blah and this converts it to FooBlah)
131         String scrubbed = spec.getType().replace("[\\.]", "");
132 
133         // compute a unique hash on the original canonical class name to guarantee
134         // the uniqueness of the generated class as well
135         int hash;
136         try {
137             MessageDigest md = MessageDigest.getInstance("MD5");
138             md.update((spec.getPackage() + "." + spec.getType()).getBytes(CHARSET));
139 
140             ByteBuffer md5 = ByteBuffer.wrap(md.digest());
141             hash = Math.abs(md5.getInt() ^ md5.getInt() ^ md5.getInt() ^ md5.getInt());
142         } catch (NoSuchAlgorithmException e) {
143             throw new RuntimeException("JVM does not support MD5", e);
144         }
145 
146         StringBuilder sb = new StringBuilder();
147         if (includePackage && !spec.getPackage().isEmpty()) {
148             sb.append(spec.getPackage()).append('.');
149         }
150         sb.append(scrubbed).append("Impl").append(hash);
151 
152         return sb.toString();
153     }
154 
155     private static final Charset CHARSET = Charset.forName("UTF-8");
156 
157     /**
158      * Generate valid Java source code for a proxy implementation of the given component
159      * type. The name and package of the generated class are consistent with the results
160      * of calling {@link #getImplementationClassName(ComponentSpecification, boolean)}. It
161      * is assumed (and not validated) that the property specification is valid and
162      * corresponds to the component type. The new class will also extend from
163      * AbstractComponent and has a single constructor that takes a ComponentRepository.
164      * <p/>
165      * The target source version generates two different outputs based on whether or not
166      * it should take advantage of post 1.5 features.
167      *
168      * @param spec          The component specification that must be implemented
169      * @param targetVersion The Java source version to target
170      *
171      * @return Source code of a valid implementation for the component type
172      */
173     public static String generateJavaCode(ComponentSpecification spec,
174                                           SourceVersion targetVersion) {
175         boolean use15 = targetVersion.compareTo(SourceVersion.RELEASE_5) >= 0;
176         String implName = getImplementationClassName(spec, false);
177 
178         // the implementation will extend AbstractComponent sans generics because
179         // Janino does not support them right now
180         StringBuilder sb = new StringBuilder();
181 
182         if (!spec.getPackage().isEmpty()) {
183             sb.append("package ").append(spec.getPackage()).append(";\n\n");
184         }
185 
186         if (use15) {
187             // prepend some annotations (and get UTC formatted date, as required by
188             // the @Generated annotation if we attach a date, which we do because it's useful)
189             TimeZone tz = TimeZone.getTimeZone("UTC");
190             DateFormat df = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'");
191             df.setTimeZone(tz);
192             String nowAsISO = df.format(new Date());
193 
194             sb.append("import javax.annotation.Generated;\n\n");
195             sb.append("@Generated(value={\"")
196               .append(ComponentFactoryProvider.class.getCanonicalName())
197               .append("\"}, date=\"").append(nowAsISO).append("\")\n");
198             sb.append("@SuppressWarnings(\"unchecked\")\n");
199         }
200 
201         sb.append("public class ").append(implName).append(" extends ")
202           .append(ABSTRACT_COMPONENT_NAME);
203         if (use15) {
204             sb.append('<').append(spec.getType()).append('>');
205         }
206         sb.append(" implements ").append(spec.getType()).append(" {\n");
207 
208         // add property instances with proper cast so we don't have to do that every
209         // time a property is accessed, and add any shared instance field declarations
210         int property = 0;
211         for (PropertyDeclaration s : spec.getProperties()) {
212             sb.append("\tprivate final ").append(s.getPropertyImplementation())
213               .append(' ').append(PROPERTY_FIELD_PREFIX).append(property).append(";\n");
214             if (s.isShared()) {
215                 sb.append("\tprivate final ").append(s.getType()).append(' ')
216                   .append(SHARED_FIELD_PREFIX).append(property).append(";\n");
217             }
218             property++;
219         }
220 
221         // define the constructor, must invoke super, assign properties, and allocate
222         // shared instances; as with type declaration we cannot use generics
223         sb.append("\n\tpublic ").append(implName).append("(").append(COMPONENT_REPO_NAME);
224         if (use15) {
225             sb.append('<').append(spec.getType()).append('>');
226         }
227 
228         sb.append(" ").append(REPO_FIELD_NAME).append(") {\n").append("\t\tsuper(")
229           .append(REPO_FIELD_NAME).append(");\n");
230         property = 0;
231         for (PropertyDeclaration s : spec.getProperties()) {
232             sb.append("\t\t").append(PROPERTY_FIELD_PREFIX).append(property)
233               .append(" = (").append(s.getPropertyImplementation()).append(") ")
234               .append(REPO_FIELD_NAME).append('.').append(GET_PROPERTY_METHOD).append('(')
235               .append(property).append(");\n");
236             if (s.isShared()) {
237                 sb.append("\t\t").append(SHARED_FIELD_PREFIX).append(property)
238                   .append(" = ").append(PROPERTY_FIELD_PREFIX).append(property)
239                   .append('.').append(CREATE_SHARE_METHOD).append("();\n");
240             }
241             property++;
242         }
243         sb.append("\t}\n");
244 
245         // implement all getters of the interface, and accumulate setters
246         Map<String, List<PropertyDeclaration>> setters = new HashMap<>();
247         property = 0;
248         for (PropertyDeclaration s : spec.getProperties()) {
249             if (use15) {
250                 sb.append("\n\t@Override");
251             }
252             appendGetter(s, property, sb);
253             List<PropertyDeclaration> setterParams = setters.get(s.getSetterMethod());
254             if (setterParams == null) {
255                 setterParams = new ArrayList<>();
256                 setters.put(s.getSetterMethod(), setterParams);
257             }
258             setterParams.add(s);
259 
260             property++;
261         }
262 
263         // implement all setters
264         for (List<PropertyDeclaration> setter : setters.values()) {
265             if (use15) {
266                 sb.append("\n\t@Override");
267             }
268             appendSetter(setter, spec, sb);
269         }
270 
271         sb.append("}\n");
272         return sb.toString();
273     }
274 
275     // magic constants used to produce the component implementation source files
276     private static final String ABSTRACT_COMPONENT_NAME = AbstractComponent.class
277             .getName();
278     private static final String COMPONENT_REPO_NAME = ComponentRepository.class.getName();
279     private static final String OBJECT_PROP_NAME = ObjectProperty.class.getName();
280 
281     private static final String REPO_FIELD_NAME = "owner";
282     private static final String INDEX_FIELD_NAME = "index";
283     private static final String GET_PROPERTY_METHOD = "getProperty";
284     private static final String CREATE_SHARE_METHOD = "createShareableInstance";
285     private static final String UPDATE_VERSION_METHOD = "incrementVersion";
286 
287     private static final String PROPERTY_FIELD_PREFIX = "property";
288     private static final String SHARED_FIELD_PREFIX = "sharedInstance";
289     private static final String SETTER_PARAM_PREFIX = "param";
290 
291     // Internal helper functions to generate the source code
292 
293     /**
294      * Append the getter method definition for the given property.
295      *
296      * @param forProperty The property whose getter method will be defined
297      * @param index       The index of the property in the overall spec
298      * @param sb          The buffer to append to
299      */
300     private static void appendGetter(PropertyDeclaration forProperty, int index,
301                                      StringBuilder sb) {
302         // method signature
303         sb.append("\n\tpublic ").append(forProperty.getType()).append(" ")
304           .append(forProperty.getGetterMethod()).append("() {\n\t\t");
305 
306         // implementation body, depending on if we use a shared instance variable or not
307         if (forProperty.isShared()) {
308             sb.append(PROPERTY_FIELD_PREFIX).append(index).append(".get(")
309               .append(INDEX_FIELD_NAME).append(", ").append(SHARED_FIELD_PREFIX)
310               .append(index).append(");\n\t\treturn ").append(SHARED_FIELD_PREFIX)
311               .append(index).append(";");
312         } else {
313             if (forProperty.getPropertyImplementation().equals(OBJECT_PROP_NAME)) {
314                 // special case where we allow ObjectProperty to have more permissive getters
315                 // and setters to support any type under the sun, but that means we have
316                 // to cast the object we get back
317                 sb.append("return (").append(forProperty.getType()).append(") ")
318                   .append(PROPERTY_FIELD_PREFIX).append(index).append(".get(")
319                   .append(INDEX_FIELD_NAME).append(");");
320             } else {
321                 sb.append("return ").append(PROPERTY_FIELD_PREFIX).append(index)
322                   .append(".get(").append(INDEX_FIELD_NAME).append(");");
323             }
324         }
325 
326         sb.append("\n\t}\n");
327     }
328 
329     /**
330      * Append the setter method definition given the property declarations that correspond
331      * to the method and its parameters.
332      *
333      * @param params The properties mutated and that define the parameters of the setter
334      *               method
335      * @param spec   The spec for the component type being generated
336      * @param sb     The buffer to append to
337      */
338     private static void appendSetter(List<PropertyDeclaration> params,
339                                      ComponentSpecification spec, StringBuilder sb) {
340         // order by parameter index
341         Collections.sort(params, new Comparator<PropertyDeclaration>() {
342             @Override
343             public int compare(PropertyDeclaration o1, PropertyDeclaration o2) {
344                 return o1.getSetterParameter() - o2.getSetterParameter();
345             }
346         });
347 
348         List<? extends PropertyDeclaration> properties = spec.getProperties();
349         String name = params.get(0).getSetterMethod();
350         boolean returnComponent = params.get(0).getSetterReturnsComponent();
351 
352         // complete method signature
353         if (returnComponent) {
354             sb.append("\n\tpublic ").append(spec.getType());
355         } else {
356             sb.append("\n\tpublic void");
357         }
358         sb.append(' ').append(name).append('(');
359 
360         // with its possibly many parameters
361         boolean first = true;
362         for (PropertyDeclaration p : params) {
363             if (first) {
364                 first = false;
365             } else {
366                 sb.append(", ");
367             }
368             sb.append(p.getType()).append(' ').append(SETTER_PARAM_PREFIX)
369               .append(properties.indexOf(p));
370         }
371         sb.append(") {\n");
372 
373         // implement the body
374         for (PropertyDeclaration p : params) {
375             int idx = properties.indexOf(p);
376             sb.append("\t\t").append(PROPERTY_FIELD_PREFIX).append(idx).append(".set(")
377               .append(INDEX_FIELD_NAME).append(", ").append(SETTER_PARAM_PREFIX)
378               .append(idx).append(");\n");
379         }
380 
381         sb.append("\t\t").append(REPO_FIELD_NAME).append('.')
382           .append(UPDATE_VERSION_METHOD).append("(").append(INDEX_FIELD_NAME)
383           .append(");\n");
384 
385         // return this component if we're not a void setter
386         if (returnComponent) {
387             sb.append("\t\treturn this;\n");
388         }
389         sb.append("\t}\n");
390     }
391 }