Michael Ludwig avatar Michael Ludwig committed b770c13

Add documentation to ComponentSpecification and related classes.

Update ComponentFactoryProvider to use SourceVersion to specify language features.

Comments (0)

Files changed (9)

entreri-core/src/main/java/com/lhkbob/entreri/impl/ComponentFactoryProvider.java

 import com.lhkbob.entreri.Component;
 import com.lhkbob.entreri.property.ObjectProperty;
 
+import javax.lang.model.SourceVersion;
 import java.nio.ByteBuffer;
 import java.nio.charset.Charset;
 import java.security.MessageDigest;
         }
 
         StringBuilder sb = new StringBuilder();
-        if (includePackage) {
+        if (includePackage && !spec.getPackage().isEmpty()) {
             sb.append(spec.getPackage()).append('.');
         }
         sb.append(scrubbed).append("Impl").append(hash);
      * corresponds to the component type. The new class will also extend from
      * AbstractComponent and has a single constructor that takes a ComponentRepository.
      * <p/>
-     * If <var>useGenerics</var> is true, the source code will use generics for
-     * AbstractComponent, its ComponentRepository, and any ObjectProperty instances
-     * present. Otherwise, raw types and casts will be placed in the source. Generics are
-     * not supported by Janino's runtime compiler.
-     * <p/>
-     * If not, undefined behavior can occur later on if the specification is inconsistent
-     * with the interface declared by the component
+     * The target source version generates two different outputs based on whether or not
+     * it should take advantage of post 1.5 features.
      *
-     * @param spec        The component specification that must be implemented
-     * @param useGenerics True if the generated source should take advantage of Java
-     *                    generics
+     * @param spec          The component specification that must be implemented
+     * @param targetVersion The Java source version to target
      *
      * @return Source code of a valid implementation for the component type
      */
-    // FIXME use the SourceVersion enum instead of useGenerics
     public static String generateJavaCode(ComponentSpecification spec,
-                                          boolean useGenerics) {
+                                          SourceVersion targetVersion) {
+        boolean use15 = targetVersion.compareTo(SourceVersion.RELEASE_5) >= 0;
         String implName = getImplementationClassName(spec, false);
 
         // the implementation will extend AbstractComponent sans generics because
         // Janino does not support them right now
         StringBuilder sb = new StringBuilder();
-        sb.append("package ").append(spec.getPackage()).append(";\n\n")
-          .append("public class ").append(implName).append(" extends ")
+
+        if (!spec.getPackage().isEmpty()) {
+            sb.append("package ").append(spec.getPackage()).append(";\n\n");
+        }
+
+        if (use15) {
+            // prepend some annotations
+            sb.append("@Generated\n");
+            sb.append("@SuppressWarnings(\"unchecked\")\n");
+        }
+
+        sb.append("public class ").append(implName).append(" extends ")
           .append(ABSTRACT_COMPONENT_NAME);
-        if (useGenerics) {
+        if (use15) {
             sb.append('<').append(spec.getType()).append('>');
         }
         sb.append(" implements ").append(spec.getType()).append(" {\n");
         // time a property is accessed, and add any shared instance field declarations
         int property = 0;
         for (PropertyDeclaration s : spec.getProperties()) {
-            String fld = (
-                    useGenerics && s.getPropertyImplementation().equals(OBJECT_PROP_NAME)
-                    ? OBJECT_PROP_NAME + "<" + s.getType() + ">"
-                    : s.getPropertyImplementation());
+            String fld = (use15 && s.getPropertyImplementation().equals(OBJECT_PROP_NAME)
+                          ? OBJECT_PROP_NAME + "<" + s.getType() + ">"
+                          : s.getPropertyImplementation());
 
             sb.append("\tprivate final ").append(fld).append(' ')
               .append(PROPERTY_FIELD_PREFIX).append(property).append(";\n");
         // define the constructor, must invoke super, assign properties, and allocate
         // shared instances; as with type declaration we cannot use generics
         sb.append("\n\tpublic ").append(implName).append("(").append(COMPONENT_REPO_NAME);
-        if (useGenerics) {
+        if (use15) {
             sb.append('<').append(spec.getType()).append('>');
         }
 
           .append(REPO_FIELD_NAME).append(");\n");
         property = 0;
         for (PropertyDeclaration s : spec.getProperties()) {
-            String cast = (
-                    useGenerics && s.getPropertyImplementation().equals(OBJECT_PROP_NAME)
-                    ? OBJECT_PROP_NAME + "<" + s.getType() + ">"
-                    : s.getPropertyImplementation());
+            String cast = (use15 && s.getPropertyImplementation().equals(OBJECT_PROP_NAME)
+                           ? OBJECT_PROP_NAME + "<" + s.getType() + ">"
+                           : s.getPropertyImplementation());
 
             sb.append("\t\t").append(PROPERTY_FIELD_PREFIX).append(property)
               .append(" = (").append(cast).append(") ").append(REPO_FIELD_NAME)
         Map<String, List<PropertyDeclaration>> setters = new HashMap<>();
         property = 0;
         for (PropertyDeclaration s : spec.getProperties()) {
-            appendGetter(s, property, sb, useGenerics);
+            appendGetter(s, property, sb, use15);
             List<PropertyDeclaration> setterParams = setters.get(s.getSetterMethod());
             if (setterParams == null) {
                 setterParams = new ArrayList<>();

entreri-core/src/main/java/com/lhkbob/entreri/impl/ComponentImplementationProcessor.java

+/*
+ * Entreri, an entity-component framework in Java
+ *
+ * Copyright (c) 2012, Michael Ludwig
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ *     Redistributions of source code must retain the above copyright notice,
+ *         this list of conditions and the following disclaimer.
+ *     Redistributions in binary form must reproduce the above copyright notice,
+ *         this list of conditions and the following disclaimer in the
+ *         documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
+ * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.lhkbob.entreri.impl;
+
+import com.lhkbob.entreri.Component;
+import com.lhkbob.entreri.IllegalComponentDefinitionException;
+
+import javax.annotation.processing.AbstractProcessor;
+import javax.annotation.processing.RoundEnvironment;
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.annotation.processing.SupportedSourceVersion;
+import javax.lang.model.SourceVersion;
+import javax.lang.model.element.Element;
+import javax.lang.model.element.ElementKind;
+import javax.lang.model.element.TypeElement;
+import javax.lang.model.util.Elements;
+import javax.lang.model.util.Types;
+import javax.tools.Diagnostic;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Set;
+
+/**
+ * ComponentImplementationProcessor is an annotation processor to use with Java 6
+ * compilers or APT to generate component proxy implementations for all component
+ * sub-interfaces encountered in the build class path. These will then be dynamically
+ * loaded at runtime instead of using Janino to generate classes from scratch.
+ *
+ * @author Michael Ludwig
+ */
+@SupportedAnnotationTypes("*")
+@SupportedSourceVersion(SourceVersion.RELEASE_7)
+public class ComponentImplementationProcessor extends AbstractProcessor {
+    @Override
+    public boolean process(Set<? extends TypeElement> annotations,
+                           RoundEnvironment roundEnv) {
+        Types tutil = processingEnv.getTypeUtils();
+        Elements eutil = processingEnv.getElementUtils();
+        for (Element e : roundEnv.getRootElements()) {
+            if (e.getKind().equals(ElementKind.INTERFACE)) {
+                // we have an interface
+                TypeElement t = (TypeElement) e;
+                if (tutil.isAssignable(t.asType(), eutil.getTypeElement(
+                        Component.class.getCanonicalName()).asType())) {
+
+                    try {
+                        ComponentSpecification spec = ComponentSpecification.Factory
+                                                                            .fromTypeElement(
+                                                                                    t,
+                                                                                    processingEnv);
+                        String name = ComponentFactoryProvider
+                                .getImplementationClassName(spec, true);
+                        String code = ComponentFactoryProvider
+                                .generateJavaCode(spec, processingEnv.getSourceVersion());
+                        try {
+                            Writer w = processingEnv.getFiler().createSourceFile(name, t)
+                                                    .openWriter();
+                            w.write(code);
+                            w.flush();
+                            w.close();
+                        } catch (IOException ioe) {
+                            processingEnv.getMessager()
+                                         .printMessage(Diagnostic.Kind.ERROR,
+                                                       ioe.getMessage(), t);
+                        }
+                    } catch (IllegalComponentDefinitionException ec) {
+                        String typePrefix = t.asType().toString();
+                        String msg = ec.getMessage();
+                        if (msg.startsWith(typePrefix)) {
+                            msg = msg.substring(typePrefix.length());
+                        }
+                        processingEnv.getMessager()
+                                     .printMessage(Diagnostic.Kind.ERROR, msg, t);
+                    }
+                }
+            }
+        }
+
+        return false;
+    }
+}

entreri-core/src/main/java/com/lhkbob/entreri/impl/ComponentSpecification.java

 import java.util.List;
 
 /**
+ * ComponentSpecification provides an interface to access the information encoded in a
+ * Component sub-interface in order to generate a proxy implementation. The specification
+ * can be extracted at runtime using reflection, or at compile time using the annotation
+ * processor mirror API. The two factory methods, {@link Factory#fromClass(Class)} and
+ * {@link Factory#fromTypeElement(javax.lang.model.element.TypeElement,
+ * javax.annotation.processing.ProcessingEnvironment)} correspond to these two scenarios.
  *
+ * @author Michael Ludwig
  */
 public interface ComponentSpecification {
+    /**
+     * Get the qualified name of the component type, with the package prefix removed.
+     * Thus, the returned string should be valid to insert into source code within the
+     * same package of the component and refer to that exact type.
+     *
+     * @return The component type
+     */
     public String getType();
 
+    /**
+     * @return The package the component type resides in
+     */
     public String getPackage();
 
+    /**
+     * Get all properties that must be implemented for this component type. This will
+     * include all properties defined in a parent component type if the type does not
+     * directly extend Component.
+     * <p/>
+     * The returned list will be immutable and sorted by logical property name.
+     *
+     * @return The list of all properties for the component
+     */
     public List<? extends PropertyDeclaration> getProperties();
 
     public static final class Factory {
         private Factory() {
         }
 
+        /**
+         * Produce a ComponentSpecification for the given Component class type.
+         *
+         * @param type The component type
+         *
+         * @return The extracted ComponentSpecification
+         *
+         * @throws com.lhkbob.entreri.IllegalComponentDefinitionException
+         *          if the class does not follow the specification defined in Component,
+         *          or if referenced Property classes do not meet their requirements
+         */
         public static ComponentSpecification fromClass(Class<? extends Component> type) {
             return new ReflectionComponentSpecification(type);
         }
 
+        /**
+         * Produce a ComponentSpecification for the given Component type element, within
+         * the context of the ProcessingEnvironment used by the active annotation
+         * processor. The returned specification maintains no dependencies to the
+         * processing environment but no guarantee is made on its validity after the
+         * processing round completes.
+         *
+         * @param type The component type
+         * @param env  The ProcessingEnvironment for the current round
+         *
+         * @return The extracted ComponentSpecification
+         *
+         * @throws com.lhkbob.entreri.IllegalComponentDefinitionException
+         *          if the class does not follow the specification defined in Component,
+         *          or if referenced Property classes do not meet ther requirements
+         */
         public static ComponentSpecification fromTypeElement(TypeElement type,
                                                              ProcessingEnvironment env) {
             return new MirrorComponentSpecification(type, env.getTypeUtils(),

entreri-core/src/main/java/com/lhkbob/entreri/impl/JaninoFactoryProvider.java

 import org.codehaus.commons.compiler.CompileException;
 import org.codehaus.janino.SimpleCompiler;
 
+import javax.lang.model.SourceVersion;
 import java.lang.reflect.Constructor;
 import java.lang.reflect.InvocationTargetException;
 
             String implName = getImplementationClassName(specification, true);
 
             // make sure to not use generics since that is not supported by janino
-            String source = generateJavaCode(specification, false);
+            String source = generateJavaCode(specification, SourceVersion.RELEASE_4);
             SimpleCompiler compiler = new SimpleCompiler();
             compiler.setParentClassLoader(getClass().getClassLoader());
 

entreri-core/src/main/java/com/lhkbob/entreri/impl/MirrorComponentSpecification.java

 import java.util.*;
 
 /**
+ * MirrorComponentSpecification is an implementation that extracts a component
+ * specification from the mirror API defined in javax.lang.model.  This should only be
+ * used in the context of an annotation processor with a valid processing environment.
  *
+ * @author Michael Ludwig
  */
-public class MirrorComponentSpecification implements ComponentSpecification {
+class MirrorComponentSpecification implements ComponentSpecification {
     private final String typeName;
     private final String packageName;
     private final List<MirrorPropertyDeclaration> properties;
 
         @Override
         public PropertyFactory<?> getPropertyFactory() {
-            throw new UnsupportedOperationException();
+            throw new UnsupportedOperationException(
+                    "Cannot create PropertyFactory with mirror API");
         }
 
         @Override

entreri-core/src/main/java/com/lhkbob/entreri/impl/PropertyDeclaration.java

 import com.lhkbob.entreri.property.PropertyFactory;
 
 /**
+ * PropertyDeclaration represents a particular "property" instance declared in a Component
+ * sub-interface. A property is represented by a bean getter method and an associated
+ * setter. This interface captures the requisite information needed to implement a
+ * component type.
  *
+ * @author Michael Ludwig
  */
 public interface PropertyDeclaration extends Comparable<PropertyDeclaration> {
+    /**
+     * Get the logical name of the property, either the name extracted from the getter
+     * bean method, or from the {@link com.lhkbob.entreri.Named} annotation.
+     *
+     * @return The property name
+     */
     public String getName();
 
+    /**
+     * Get the canonical class name of the type of value stored by this property.
+     *
+     * @return The type of this property, suitable for inclusion in source code
+     */
     public String getType();
 
+    /**
+     * Get the canonical class name of the {@link com.lhkbob.entreri.property.Property
+     * Property} implementation corresponding to the type of this property.
+     *
+     * @return The property implementation, suitable for inclusion in source code
+     */
     public String getPropertyImplementation();
 
+    /**
+     * Get the name of the setter method that will be used to modify this property value.
+     * Multiply properties may use the same setter, and {@link #getSetterParameter()}
+     * determines their order in the signature. All properties in a ComponentSpecification
+     * that share a setter name use the same setter method.
+     *
+     * @return The name of the setter method
+     */
     public String getSetterMethod();
 
+    /**
+     * Get the name of the getter method that is used to access the value of this
+     * property. Its return type is equal to {@link #getType()} and it will not take any
+     * arguments.
+     *
+     * @return The bean getter method
+     */
     public String getGetterMethod();
 
+    /**
+     * Get the parameter index used by this property in the signature of its corresponding
+     * setter method. This will be 0 for properties that use the standard bean setter
+     * method, but may be different when properties share the same setter with multiple
+     * parameters.
+     *
+     * @return The method parameter corresponding to this property
+     */
     public int getSetterParameter();
 
+    /**
+     * Get whether or not the signature of the setter method of this property returns the
+     * owning Component or if it returns void.  When multiple properties have the same
+     * setter method name, this will always agree.
+     *
+     * @return True if the method signature returns the component
+     */
     public boolean getSetterReturnsComponent();
 
+    /**
+     * Get whether or not this property should use the shared instance API to store and
+     * get values of the property.
+     *
+     * @return True if the getter was annotated with {@link com.lhkbob.entreri.SharedInstance}
+     */
     public boolean isShared();
 
+    /**
+     * Get the PropertyFactory instance that was configured for this property. It must be
+     * used to instantiate the property objects that will be assignable to the type
+     * returned by {@link #getPropertyImplementation()}, and will be configured by all
+     * attribute annotations applied to the getter method.
+     * <p/>
+     * This is only available during a runtime situation, and should not be called when
+     * ComponentSpecification came from the mirror API.
+     *
+     * @return The property factory for this property
+     */
     public PropertyFactory<?> getPropertyFactory();
 }

entreri-core/src/main/java/com/lhkbob/entreri/impl/ReflectionComponentSpecification.java

 import java.util.*;
 
 /**
+ * ReflectionComponentSpecification is an implementation that extracts the component
+ * specification from a {@link Class} object using the reflection APIs defined by Java.
+ * This can only be used after the referenced classes have been compiled and are capable
+ * of being loaded, e.g. the opposite scenario from MirrorComponentSpecification.
  *
+ * @author Michael Ludwig
  */
-public class ReflectionComponentSpecification implements ComponentSpecification {
+class ReflectionComponentSpecification implements ComponentSpecification {
     private final Class<? extends Component> type;
     private final List<ReflectionPropertyDeclaration> properties;
 

entreri-core/src/main/java/com/lhkbob/entreri/impl/StaticComponentGenerator.java

-/*
- * Entreri, an entity-component framework in Java
- *
- * Copyright (c) 2012, Michael Ludwig
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without modification,
- * are permitted provided that the following conditions are met:
- *
- *     Redistributions of source code must retain the above copyright notice,
- *         this list of conditions and the following disclaimer.
- *     Redistributions in binary form must reproduce the above copyright notice,
- *         this list of conditions and the following disclaimer in the
- *         documentation and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
- * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
- * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
- * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
- * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
- * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
-package com.lhkbob.entreri.impl;
-
-import com.lhkbob.entreri.IllegalComponentDefinitionException;
-
-import javax.annotation.processing.AbstractProcessor;
-import javax.annotation.processing.RoundEnvironment;
-import javax.annotation.processing.SupportedAnnotationTypes;
-import javax.annotation.processing.SupportedSourceVersion;
-import javax.lang.model.SourceVersion;
-import javax.lang.model.element.Element;
-import javax.lang.model.element.ElementKind;
-import javax.lang.model.element.TypeElement;
-import javax.lang.model.util.Elements;
-import javax.lang.model.util.Types;
-import javax.tools.Diagnostic;
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Set;
-
-/**
- *
- */
-@SupportedAnnotationTypes("*")
-@SupportedSourceVersion(SourceVersion.RELEASE_7)
-public class StaticComponentGenerator extends AbstractProcessor {
-    @Override
-    public boolean process(Set<? extends TypeElement> annotations,
-                           RoundEnvironment roundEnv) {
-        Types tutil = processingEnv.getTypeUtils();
-        Elements eutil = processingEnv.getElementUtils();
-        for (Element e : roundEnv.getRootElements()) {
-            if (e.getKind().equals(ElementKind.INTERFACE)) {
-                // we have an interface
-                TypeElement t = (TypeElement) e;
-                if (tutil.isAssignable(t.asType(), eutil.getTypeElement(
-                        "com.lhkbob.entreri.Component").asType())) {
-
-                    try {
-                        ComponentSpecification spec = ComponentSpecification.Factory
-                                                                            .fromTypeElement(
-                                                                                    t,
-                                                                                    processingEnv);
-                        String name = ComponentFactoryProvider
-                                .getImplementationClassName(spec, true);
-                        String code = ComponentFactoryProvider
-                                .generateJavaCode(spec, true);
-                        try {
-                            Writer w = processingEnv.getFiler().createSourceFile(name, t)
-                                                    .openWriter();
-                            w.write(code);
-                            w.flush();
-                            w.close();
-                        } catch (IOException ioe) {
-                            processingEnv.getMessager()
-                                         .printMessage(Diagnostic.Kind.ERROR,
-                                                       ioe.getMessage(), t);
-                        }
-                    } catch (IllegalComponentDefinitionException ec) {
-                        String typePrefix = t.asType().toString();
-                        String msg = ec.getMessage();
-                        if (msg.startsWith(typePrefix)) {
-                            msg = msg.substring(typePrefix.length());
-                        }
-                        processingEnv.getMessager()
-                                     .printMessage(Diagnostic.Kind.ERROR, msg, t);
-                    }
-                }
-            }
-        }
-
-        return false;
-    }
-}

entreri-core/src/main/resources/META-INF/services/javax.annotation.processing.Processor

-com.lhkbob.entreri.impl.StaticComponentGenerator
+com.lhkbob.entreri.impl.ComponentImplementationProcessor
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.