Repository: incubator-batchee
Updated Branches:
  refs/heads/master a36d37bbb -> e87abcf68


adding documentation mojo


Project: http://git-wip-us.apache.org/repos/asf/incubator-batchee/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-batchee/commit/e87abcf6
Tree: http://git-wip-us.apache.org/repos/asf/incubator-batchee/tree/e87abcf6
Diff: http://git-wip-us.apache.org/repos/asf/incubator-batchee/diff/e87abcf6

Branch: refs/heads/master
Commit: e87abcf68da008a061df26d9a0601cb0a71d1421
Parents: a36d37b
Author: Romain Manni-Bucau <[email protected]>
Authored: Tue Dec 1 17:10:23 2015 +0100
Committer: Romain Manni-Bucau <[email protected]>
Committed: Tue Dec 1 17:10:23 2015 +0100

----------------------------------------------------------------------
 tools/doc-api/pom.xml                           |  33 ++
 .../apache/batchee/doc/api/Configuration.java   |  32 ++
 tools/maven-plugin/pom.xml                      |  10 +
 .../batchee/tools/maven/DocumentationMojo.java  | 333 +++++++++++++++++++
 .../tools/maven/DocumentationMojoTest.java      |  79 +++++
 tools/pom.xml                                   |   1 +
 6 files changed, 488 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/doc-api/pom.xml
----------------------------------------------------------------------
diff --git a/tools/doc-api/pom.xml b/tools/doc-api/pom.xml
new file mode 100644
index 0000000..3265636
--- /dev/null
+++ b/tools/doc-api/pom.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+  Licensed to the Apache Software Foundation (ASF) under one or more
+  contributor license agreements.  See the NOTICE file distributed with
+  this work for additional information regarding copyright ownership.
+  The ASF licenses this file to You under the Apache License, Version 2.0
+  (the "License"); you may not use this file except in compliance with
+  the License.  You may obtain a copy of the License at
+
+      http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0";
+         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance";
+         xsi:schemaLocation="
+          http://maven.apache.org/POM/4.0.0
+          http://maven.apache.org/xsd/maven-4.0.0.xsd";>
+  <modelVersion>4.0.0</modelVersion>
+
+  <parent>
+    <artifactId>batchee-tools</artifactId>
+    <groupId>org.apache.batchee</groupId>
+    <version>0.3-incubating-SNAPSHOT</version>
+  </parent>
+
+  <artifactId>batchee-doc-api</artifactId>
+  <name>BatchEE :: Tools :: Doc API</name>
+</project>

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/doc-api/src/main/java/org/apache/batchee/doc/api/Configuration.java
----------------------------------------------------------------------
diff --git 
a/tools/doc-api/src/main/java/org/apache/batchee/doc/api/Configuration.java 
b/tools/doc-api/src/main/java/org/apache/batchee/doc/api/Configuration.java
new file mode 100644
index 0000000..2de07f8
--- /dev/null
+++ b/tools/doc-api/src/main/java/org/apache/batchee/doc/api/Configuration.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.batchee.doc.api;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+@Retention(CLASS)
+@Target({ FIELD, METHOD, PARAMETER, TYPE })
+public @interface Configuration {
+    String value();
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/maven-plugin/pom.xml
----------------------------------------------------------------------
diff --git a/tools/maven-plugin/pom.xml b/tools/maven-plugin/pom.xml
index 057180d..bdb140f 100644
--- a/tools/maven-plugin/pom.xml
+++ b/tools/maven-plugin/pom.xml
@@ -36,6 +36,11 @@
     </dependency>
     <dependency>
       <groupId>org.apache.batchee</groupId>
+      <artifactId>batchee-doc-api</artifactId>
+      <version>${project.version}</version>
+    </dependency>
+    <dependency>
+      <groupId>org.apache.batchee</groupId>
       <artifactId>batchee-jbatch</artifactId>
       <version>${project.version}</version>
     </dependency>
@@ -54,6 +59,11 @@
       <artifactId>jung-visualization</artifactId>
       <version>${jung.version}</version>
     </dependency>
+    <dependency>
+      <groupId>org.ow2.asm</groupId>
+      <artifactId>asm</artifactId>
+      <version>5.0.4</version>
+    </dependency>
 
     <dependency>
       <groupId>org.apache.maven</groupId>

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/maven-plugin/src/main/java/org/apache/batchee/tools/maven/DocumentationMojo.java
----------------------------------------------------------------------
diff --git 
a/tools/maven-plugin/src/main/java/org/apache/batchee/tools/maven/DocumentationMojo.java
 
b/tools/maven-plugin/src/main/java/org/apache/batchee/tools/maven/DocumentationMojo.java
new file mode 100644
index 0000000..c1df82b
--- /dev/null
+++ 
b/tools/maven-plugin/src/main/java/org/apache/batchee/tools/maven/DocumentationMojo.java
@@ -0,0 +1,333 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.batchee.tools.maven;
+
+import org.apache.maven.plugin.AbstractMojo;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.apache.maven.plugins.annotations.LifecyclePhase;
+import org.apache.maven.plugins.annotations.Mojo;
+import org.apache.maven.plugins.annotations.Parameter;
+import org.objectweb.asm.AnnotationVisitor;
+import org.objectweb.asm.ClassReader;
+import org.objectweb.asm.ClassVisitor;
+import org.objectweb.asm.FieldVisitor;
+
+import java.beans.Introspector;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+
+import static org.objectweb.asm.ClassReader.SKIP_CODE;
+import static org.objectweb.asm.ClassReader.SKIP_DEBUG;
+import static org.objectweb.asm.ClassReader.SKIP_FRAMES;
+import static org.objectweb.asm.Opcodes.ASM5;
+
+@Mojo(name = "doc", defaultPhase = LifecyclePhase.PROCESS_CLASSES)
+public class DocumentationMojo extends AbstractMojo {
+    private static final String PROPERTY_MARKER = 
"Ljavax/batch/api/BatchProperty;";
+    private static final String NAMED_MARKER = "Ljavax/inject/Named;";
+    private static final String INJECT_MARKER = "Ljavax/inject/Inject;";
+    private static final String CONFIGURATION_MARKER = 
"Lorg/apache/batchee/doc/api/Configuration;";
+
+    @Parameter(property = "batchee.classes", defaultValue = 
"${project.build.outputDirectory}")
+    protected File classes;
+
+    @Parameter(property = "batchee.output", defaultValue = 
"${project.build.directory}/generated-docs/${project.artifactId}-jbatch.adoc")
+    protected File output;
+
+    @Parameter(property = "batchee.formatter", defaultValue = "adoc")
+    protected String formatter;
+
+    @Override
+    public void execute() throws MojoExecutionException, MojoFailureException {
+        if (classes == null || !classes.isDirectory()) {
+            getLog().warn((classes != null ? classes.getAbsolutePath() : 
"null") + " is not a directory, skipping");
+            return;
+        }
+
+        // instantiate the formatter now to avoid to scan for nothing if we 
can't instantiate it
+        final Formatter formatterInstance = createFormatter();
+
+        // find meta
+        final Map<String, Collection<FieldDoc>> configByComponent = new 
TreeMap<String, Collection<FieldDoc>>();
+        try {
+            scan(configByComponent, classes);
+        } catch (final IOException e) {
+            throw new MojoExecutionException(e.getMessage(), e);
+        }
+
+        if (configByComponent.isEmpty()) {
+            getLog().warn("Nothing found, maybe adjust <classes>, skipping.");
+            return;
+        }
+
+        // format what we found
+        if (!output.getParentFile().isDirectory() && 
!output.getParentFile().mkdirs()) {
+            throw new MojoExecutionException("Can't create " + 
output.getAbsolutePath());
+        }
+        FileWriter writer = null;
+        try {
+            writer = new FileWriter(output);
+            formatterInstance.begin(writer);
+            for (final Map.Entry<String, Collection<FieldDoc>> component : 
configByComponent.entrySet()) {
+                formatterInstance.beginClass(writer, component.getKey());
+                for (final FieldDoc doc : component.getValue()) {
+                    formatterInstance.add(writer, doc);
+                }
+                formatterInstance.endClass(writer);
+            }
+            formatterInstance.end(writer);
+        } catch (final IOException e) {
+            throw new MojoExecutionException(e.getMessage(), e);
+        } finally {
+            if (writer != null) {
+                try {
+                    writer.close();
+                } catch (final IOException e) {
+                    // no-op
+                }
+            }
+        }
+    }
+
+    private Formatter createFormatter() {
+        if (formatter == null || formatter.startsWith("adoc")) {
+            final int level = "adoc".equals(formatter) || formatter == null ? 
1 : Integer.parseInt(formatter.substring("adoc".length()));
+            final String prefix;
+            {
+                final StringBuilder builder = new StringBuilder();
+                for (int i = 0; i < level; i++) {
+                    builder.append("=");
+                }
+                prefix = builder.toString();
+            }
+
+            return new Formatter() {
+                @Override
+                public void begin(final Writer writer) throws IOException {
+                    // no-op
+                }
+
+                @Override
+                public void beginClass(final Writer writer, final String 
className) throws IOException {
+                    writer.append(prefix).append(" 
").append(className).append("\n\n|===\n|Name|Description\n");
+                }
+
+                @Override
+                public void add(final Writer writer, final FieldDoc doc) 
throws IOException {
+                    
writer.append("|").append(doc.getName()).append("|").append(doc.getDoc() == 
null ? "-" : doc.getDoc()).append("\n");
+                }
+
+                @Override
+                public void endClass(final Writer writer) throws IOException {
+                    writer.append("|===\n\n");
+                }
+
+                @Override
+                public void end(final Writer writer) throws IOException {
+                    // no-op
+                }
+            };
+        }
+        try {
+            return 
Formatter.class.cast(Thread.currentThread().getContextClassLoader().loadClass(formatter.trim()).newInstance());
+        } catch (final InstantiationException e) {
+            throw new IllegalArgumentException(e);
+        } catch (final IllegalAccessException e) {
+            throw new IllegalArgumentException(e);
+        } catch (final ClassNotFoundException e) {
+            throw new IllegalArgumentException(e);
+        }
+    }
+
+    private void scan(final Map<String, Collection<FieldDoc>> commands, final 
File file) throws IOException {
+        if (file.isFile()) {
+            if (file.getName().endsWith(".class")) {
+                component(commands, file);
+            } // else we don't care
+        } else if (file.isDirectory()) {
+            final File[] children = file.listFiles();
+            if (children != null) {
+                for (final File child : children) {
+                    scan(commands, child);
+                }
+            }
+        }
+    }
+
+    private String component(final Map<String, Collection<FieldDoc>> commands, 
final File classFile) throws IOException {
+        InputStream stream = null;
+        try {
+            stream = new FileInputStream(classFile);
+            final ClassReader reader = new ClassReader(stream);
+            reader.accept(new ClassVisitor(ASM5) {
+                private String className;
+                private List<FieldDoc> configs;
+
+                @Override
+                public void visit(final int version, final int access, final 
String name, final String signature, final String superName, final String[] 
interfaces) {
+                    className = name.replace('/', '.');
+                }
+
+                @Override
+                public AnnotationVisitor visitAnnotation(final String desc, 
final boolean visible) {
+                    final AnnotationVisitor annotationVisitor = 
super.visitAnnotation(desc, visible);
+                    if (NAMED_MARKER.equals(desc)) {
+                        final int dollar = className.lastIndexOf('$');
+                        if (dollar > 0) {
+                            className = className.substring(dollar + 1);
+                        } else {
+                            final int dot = className.lastIndexOf('.');
+                            if (dot > 0) {
+                                className = className.substring(dot + 1);
+                            }
+                        }
+
+                        className = Introspector.decapitalize(className);
+                        return new AnnotationVisitor(ASM5, annotationVisitor) {
+                            @Override
+                            public void visit(final String name, final Object 
value) {
+                                super.visit(name, value);
+                                if ("value".equals(name) && 
String.class.isInstance(value) && !String.class.cast(value).isEmpty()) {
+                                    className = value.toString();
+                                }
+                            }
+                        };
+                    }
+                    return annotationVisitor;
+                }
+
+                @Override
+                public FieldVisitor visitField(final int access, final String 
name, final String desc, final String signature, final Object value) {
+                    return new FieldVisitor(ASM5, super.visitField(access, 
name, desc, signature, value)) {
+                        private boolean marked = false;
+                        private boolean hasInject = false;
+                        private String configName = name;
+                        private String doc = null;
+
+                        @Override
+                        public AnnotationVisitor visitAnnotation(final String 
desc, final boolean visible) {
+                            final AnnotationVisitor annotationVisitor = 
super.visitAnnotation(desc, visible);
+                            if (PROPERTY_MARKER.equals(desc)) {
+                                marked = true;
+                                return new AnnotationVisitor(ASM5, 
annotationVisitor) {
+                                    @Override
+                                    public void visit(final String name, final 
Object value) {
+                                        super.visit(name, value);
+                                        if ("name".equals(name) && 
String.class.isInstance(value) && !String.class.cast(value).isEmpty()) {
+                                            configName = value.toString();
+                                        }
+                                    }
+                                };
+                            }
+                            if (INJECT_MARKER.equals(desc)) {
+                                hasInject = true;
+                                return annotationVisitor;
+                            }
+                            if (CONFIGURATION_MARKER.equals(desc)) {
+                                return new AnnotationVisitor(ASM5, 
annotationVisitor) {
+                                    @Override
+                                    public void visit(final String name, final 
Object value) {
+                                        super.visit(name, value);
+                                        if ("value".equals(name) && 
String.class.isInstance(value) && !String.class.cast(value).isEmpty()) {
+                                            doc = value.toString();
+                                        }
+                                    }
+                                };
+                            }
+                            return annotationVisitor;
+                        }
+
+                        @Override
+                        public void visitEnd() {
+                            super.visitEnd();
+                            if (marked && hasInject) {
+                                if (configs == null) {
+                                    configs = new ArrayList<FieldDoc>();
+                                }
+                                configs.add(new FieldDoc(configName, doc));
+                            }
+                        }
+                    };
+                }
+
+                @Override
+                public void visitEnd() {
+                    super.visitEnd();
+                    if (configs != null) {
+                        Collections.sort(configs);
+                        commands.put(className, configs);
+                    }
+                }
+            }, SKIP_CODE + SKIP_DEBUG + SKIP_FRAMES);
+        } finally {
+            try {
+                if (stream != null) {
+                    stream.close();
+                }
+            } catch (final IOException e) {
+                // no-op
+            }
+        }
+        return null;
+    }
+
+    public static class FieldDoc implements Comparable<FieldDoc> {
+        private final String name;
+        private final String doc;
+
+        private FieldDoc(final String name, final String doc) {
+            this.name = name;
+            this.doc = doc;
+        }
+
+        @Override
+        public int compareTo(final FieldDoc o) {
+            return name.compareTo(o.name);
+        }
+
+        public String getName() {
+            return name;
+        }
+
+        public String getDoc() {
+            return doc;
+        }
+    }
+
+    public interface Formatter {
+        void begin(Writer writer) throws IOException;
+
+        void beginClass(Writer writer, String className) throws IOException;
+
+        void add(Writer writer, FieldDoc doc) throws IOException;
+
+        void endClass(Writer writer) throws IOException;
+
+        void end(Writer writer) throws IOException;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/maven-plugin/src/test/java/org/apache/batchee/tools/maven/DocumentationMojoTest.java
----------------------------------------------------------------------
diff --git 
a/tools/maven-plugin/src/test/java/org/apache/batchee/tools/maven/DocumentationMojoTest.java
 
b/tools/maven-plugin/src/test/java/org/apache/batchee/tools/maven/DocumentationMojoTest.java
new file mode 100644
index 0000000..4e4dd66
--- /dev/null
+++ 
b/tools/maven-plugin/src/test/java/org/apache/batchee/tools/maven/DocumentationMojoTest.java
@@ -0,0 +1,79 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.batchee.tools.maven;
+
+import org.apache.batchee.doc.api.Configuration;
+import org.apache.maven.plugin.MojoExecutionException;
+import org.apache.maven.plugin.MojoFailureException;
+import org.codehaus.plexus.util.IOUtil;
+import org.junit.Test;
+
+import javax.batch.api.BatchProperty;
+import javax.inject.Inject;
+import javax.inject.Named;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+public class DocumentationMojoTest {
+    @Test
+    public void document() throws MojoFailureException, 
MojoExecutionException, IOException {
+        final File out = new File("target/DocumentationMojoTest/output.adoc");
+        new DocumentationMojo() {{
+            classes = new 
File("target/test-classes/org/apache/batchee/tools/maven/");
+            output = out;
+        }}.execute();
+        final FileInputStream fis = new FileInputStream(out);
+        assertEquals("= myComponent\n" +
+            "\n" +
+            "|===\n" +
+            "|Name|Description\n" +
+            "|configByDefault|this is an important config\n" +
+            "|expl|this one is less important\n" +
+            "|===\n" +
+            "\n" +
+            "= org.apache.batchee.tools.maven.batchlet.SimpleBatchlet\n" +
+            "\n" +
+            "|===\n" +
+            "|Name|Description\n" +
+            "|fail|-\n" +
+            "|sleep|-\n" +
+            "|===\n" +
+            "\n", IOUtil.toString(fis));
+        fis.close();
+    }
+
+    @Named
+    public static class MyComponent {
+        @Inject
+        @BatchProperty
+        @Configuration("this is an important config")
+        private String configByDefault;
+
+        @Inject
+        @BatchProperty(name = "expl")
+        @Configuration("this one is less important")
+        private String explicit;
+
+        private String ignored1;
+
+        @BatchProperty
+        private String ignored;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-batchee/blob/e87abcf6/tools/pom.xml
----------------------------------------------------------------------
diff --git a/tools/pom.xml b/tools/pom.xml
index 4001719..1f5f04f 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -32,5 +32,6 @@
         <module>maven-plugin</module>
         <module>cli</module>
         <module>ee6</module>
+        <module>doc-api</module>
     </modules>
 </project>

Reply via email to