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>
