From 9b4b04074655c5b7c227e545e364ff677d072c86 Mon Sep 17 00:00:00 2001
From: glelouet
Date: Wed, 22 Jan 2020 23:29:04 +0100
Subject: [PATCH] made processors for @generated and @author.
see #80.
---
.../com/helger/jcodemodel/JCodeModel.java | 116 +++++++++++-----
.../preprocess/AJCodePreprocessor.java | 49 +++++++
.../preprocess/AuthorProcessor.java | 88 ++++++++++++
.../preprocess/GeneratedProcessor.java | 126 ++++++++++++++++++
.../helger/jcodemodel/writer/JCMWriter.java | 55 +++++---
.../preprocess/AuthorPreprocessorTest.java | 30 +++++
.../preprocess/GeneratedPreprocessorTest.java | 31 +++++
7 files changed, 444 insertions(+), 51 deletions(-)
create mode 100644 src/main/java/com/helger/jcodemodel/preprocess/AJCodePreprocessor.java
create mode 100644 src/main/java/com/helger/jcodemodel/preprocess/AuthorProcessor.java
create mode 100644 src/main/java/com/helger/jcodemodel/preprocess/GeneratedProcessor.java
create mode 100644 src/test/java/com/helger/jcodemodel/preprocess/AuthorPreprocessorTest.java
create mode 100644 src/test/java/com/helger/jcodemodel/preprocess/GeneratedPreprocessorTest.java
diff --git a/src/main/java/com/helger/jcodemodel/JCodeModel.java b/src/main/java/com/helger/jcodemodel/JCodeModel.java
index db849db9..f545eaf5 100644
--- a/src/main/java/com/helger/jcodemodel/JCodeModel.java
+++ b/src/main/java/com/helger/jcodemodel/JCodeModel.java
@@ -44,8 +44,10 @@
import java.io.IOException;
import java.io.PrintStream;
import java.io.Serializable;
+import java.lang.reflect.InvocationTargetException;
import java.nio.charset.Charset;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
@@ -64,6 +66,7 @@
import com.helger.jcodemodel.meta.CodeModelBuildingException;
import com.helger.jcodemodel.meta.ErrorTypeFound;
import com.helger.jcodemodel.meta.JCodeModelJavaxLangModelAdapter;
+import com.helger.jcodemodel.preprocess.AJCodePreprocessor;
import com.helger.jcodemodel.util.EFileSystemConvention;
import com.helger.jcodemodel.util.FSName;
import com.helger.jcodemodel.util.IFileSystemConvention;
@@ -154,8 +157,9 @@ public static boolean isFileSystemCaseSensitive ()
m1.put (Void.class, Void.TYPE);
// Swap keys and values
- for (final Map.Entry , Class >> e : m1.entrySet ())
+ for (final Map.Entry , Class >> e : m1.entrySet ()) {
m2.put (e.getValue (), e.getKey ());
+ }
s_aBoxToPrimitive = Collections.unmodifiableMap (m1);
s_aPrimitiveToBox = Collections.unmodifiableMap (m2);
@@ -234,8 +238,9 @@ public final IFileSystemConvention getFileSystemConvention ()
public final JCodeModel setFileSystemConvention (@Nonnull final IFileSystemConvention aFSConvention) throws JCodeModelException
{
JCValueEnforcer.notNull (aFSConvention, "FSConvention");
- if (!m_aPackages.isEmpty () || !m_aResourceDirs.isEmpty ())
+ if (!m_aPackages.isEmpty () || !m_aResourceDirs.isEmpty ()) {
throw new JCodeModelException ("The FileSystem convention cannot be changed if a package or a resource directory already exists.");
+ }
m_aFSConvention = aFSConvention;
return this;
}
@@ -298,8 +303,9 @@ private static String _unifyPath (@Nonnull final String sName)
@Nonnull
private FSName _createFSName (@Nonnull final String sName)
{
- if (m_aFSConvention.isCaseSensistive ())
+ if (m_aFSConvention.isCaseSensistive ()) {
return FSName.createCaseSensitive (sName);
+ }
return FSName.createCaseInsensitive (sName);
}
@@ -325,8 +331,9 @@ public JResourceDir resourceDir (@Nonnull final String sName) throws JCodeModelE
final String sCleanPath = _unifyPath (sName);
// 2. consistency checks
- if (sCleanPath.startsWith (JResourceDir.SEPARATOR_STR))
+ if (sCleanPath.startsWith (JResourceDir.SEPARATOR_STR)) {
throw new IllegalArgumentException ("A resource directory may not be an absolute path: '" + sName + "'");
+ }
// 3. ensure root is present
final JResourceDir aRootDir = m_aResourceDirs.computeIfAbsent (_createFSName (""), k -> JResourceDir.root (this));
@@ -337,18 +344,20 @@ public JResourceDir resourceDir (@Nonnull final String sName) throws JCodeModelE
JResourceDir aCur = aRootDir;
for (final String sPart : JCStringHelper.getExplodedArray (JResourceDir.SEPARATOR, sCleanPath))
{
- if (sDirName.length () > 0)
+ if (sDirName.length () > 0) {
sDirName += JResourceDir.SEPARATOR;
+ }
sDirName += sPart;
// Check if directory has a file with the name
- if (aParentDir.hasResourceFile (sPart))
+ if (aParentDir.hasResourceFile (sPart)) {
throw new JResourceAlreadyExistsException (aParentDir.fullChildName (sPart));
+ }
// Get main subdir
final JResourceDir aFinalParentDir = aParentDir;
aCur = m_aResourceDirs.computeIfAbsent (_createFSName (sDirName),
- k -> new JResourceDir (this, aFinalParentDir, k.getName ()));
+ k -> new JResourceDir (this, aFinalParentDir, k.getName ()));
aParentDir = aCur;
}
@@ -375,8 +384,9 @@ public JResourceDir rootResourceDir ()
public boolean containsResourceDir (@Nullable final String sAbsolutePath)
{
- if (sAbsolutePath == null)
+ if (sAbsolutePath == null) {
return false;
+ }
// 1. unify name
final String sCleanPath = _unifyPath (sAbsolutePath);
// 2. check existence
@@ -419,16 +429,17 @@ public List getAllResourceDirs ()
*/
@Nonnull
public JDefinedClass _class (final int nMods,
- @Nonnull final String sFullyQualifiedClassName,
- @Nonnull final EClassType eClassType) throws JCodeModelException
+ @Nonnull final String sFullyQualifiedClassName,
+ @Nonnull final EClassType eClassType) throws JCodeModelException
{
final int nIdx = sFullyQualifiedClassName.lastIndexOf (JPackage.SEPARATOR);
- if (nIdx < 0)
+ if (nIdx < 0) {
return rootPackage ()._class (nMods, sFullyQualifiedClassName, eClassType);
+ }
return _package (sFullyQualifiedClassName.substring (0, nIdx))._class (nMods,
- sFullyQualifiedClassName.substring (nIdx +
- 1),
- eClassType);
+ sFullyQualifiedClassName.substring (nIdx +
+ 1),
+ eClassType);
}
/**
@@ -459,7 +470,7 @@ public JDefinedClass _class (@Nonnull final String sFullyQualifiedClassName) thr
*/
@Nonnull
public JDefinedClass _class (final int nMods,
- @Nonnull final String sFullyQualifiedClassName) throws JCodeModelException
+ @Nonnull final String sFullyQualifiedClassName) throws JCodeModelException
{
return _class (nMods, sFullyQualifiedClassName, EClassType.CLASS);
}
@@ -477,7 +488,7 @@ public JDefinedClass _class (final int nMods,
*/
@Nonnull
public JDefinedClass _class (@Nonnull final String sFullyQualifiedClassName,
- @Nonnull final EClassType eClassType) throws JCodeModelException
+ @Nonnull final EClassType eClassType) throws JCodeModelException
{
return _class (JMod.PUBLIC, sFullyQualifiedClassName, eClassType);
}
@@ -597,9 +608,11 @@ public JErrorClass errorClass (@Nonnull final String sMessage, @Nullable final S
public boolean buildsErrorTypeRefs ()
{
// avoid concurrent modification exception
- for (final JPackage aPackage : getAllPackages ())
- if (aPackage.buildsErrorTypeRefs ())
+ for (final JPackage aPackage : getAllPackages ()) {
+ if (aPackage.buildsErrorTypeRefs ()) {
return true;
+ }
+ }
return false;
}
@@ -615,10 +628,11 @@ public boolean buildsErrorTypeRefs ()
public JDefinedClass _getClass (@Nonnull final String sFullyQualifiedClassName)
{
final int nIndex = sFullyQualifiedClassName.lastIndexOf (JPackage.SEPARATOR);
- if (nIndex < 0)
+ if (nIndex < 0) {
return rootPackage ()._getClass (sFullyQualifiedClassName);
+ }
return _package (sFullyQualifiedClassName.substring (0,
- nIndex))._getClass (sFullyQualifiedClassName.substring (nIndex + 1));
+ nIndex))._getClass (sFullyQualifiedClassName.substring (nIndex + 1));
}
/**
@@ -752,8 +766,8 @@ public void build (@Nonnull final File aDestDir, @Nullable final PrintStream aSt
@Deprecated
@ChangeInV4
public void build (@Nonnull final File aSrcDir,
- @Nonnull final File aResourceDir,
- @Nullable final PrintStream aStatusPS) throws IOException
+ @Nonnull final File aResourceDir,
+ @Nullable final PrintStream aStatusPS) throws IOException
{
AbstractCodeWriter res = new FileCodeWriter (aResourceDir, m_aBuildingCharset, m_sBuildingNewLine);
AbstractCodeWriter src = new FileCodeWriter (aSrcDir, m_aBuildingCharset, m_sBuildingNewLine);
@@ -833,7 +847,7 @@ public void build (@Nonnull final AbstractCodeWriter aWriter) throws IOException
@Deprecated
@ChangeInV4
public void build (@Nonnull final AbstractCodeWriter aSource,
- @Nonnull final AbstractCodeWriter aResource) throws IOException
+ @Nonnull final AbstractCodeWriter aResource) throws IOException
{
new JCMWriter (this).setCharset (m_aBuildingCharset).setNewLine (m_sBuildingNewLine).build (aSource, aResource);
}
@@ -847,10 +861,12 @@ public int countArtifacts ()
{
int r = 0;
// avoid concurrent modification exception
- for (final JPackage aItem : new ArrayList <> (m_aPackages.values ()))
+ for (final JPackage aItem : new ArrayList <> (m_aPackages.values ())) {
r += aItem.countArtifacts ();
- for (final JResourceDir aItem : new ArrayList <> (m_aResourceDirs.values ()))
+ }
+ for (final JResourceDir aItem : new ArrayList <> (m_aResourceDirs.values ())) {
r += aItem.countArtifacts ();
+ }
return r;
}
@@ -921,7 +937,7 @@ public AbstractJClass ref (@Nonnull final Class > aClazz)
*/
@Nonnull
public JDefinedClass ref (@Nonnull final TypeElement aElement,
- @Nonnull final Elements aElementUtils) throws ErrorTypeFound, CodeModelBuildingException
+ @Nonnull final Elements aElementUtils) throws ErrorTypeFound, CodeModelBuildingException
{
final JCodeModelJavaxLangModelAdapter adapter = new JCodeModelJavaxLangModelAdapter (this, aElementUtils);
return adapter.getClass (aElement);
@@ -958,7 +974,7 @@ public JDefinedClass ref (@Nonnull final TypeElement aElement,
*/
@Nonnull
public JDefinedClass refWithErrorTypes (@Nonnull final TypeElement aElement,
- @Nonnull final Elements aElementUtils) throws CodeModelBuildingException
+ @Nonnull final Elements aElementUtils) throws CodeModelBuildingException
{
final JCodeModelJavaxLangModelAdapter adapter = new JCodeModelJavaxLangModelAdapter (this, aElementUtils);
return adapter.getClassWithErrorTypes (aElement);
@@ -976,8 +992,9 @@ public JDefinedClass refWithErrorTypes (@Nonnull final TypeElement aElement,
@Nonnull
public AbstractJType _ref (@Nonnull final Class > aClass)
{
- if (aClass.isPrimitive ())
+ if (aClass.isPrimitive ()) {
return AbstractJType.parse (this, aClass.getName ());
+ }
return ref (aClass);
}
@@ -1118,16 +1135,17 @@ AbstractJClass parseTypeName ()
// not supported
throw new IllegalArgumentException ("only extends/super can follow ?, but found " +
- m_sTypeName.substring (m_nIdx));
+ m_sTypeName.substring (m_nIdx));
}
while (m_nIdx < m_sTypeName.length ())
{
final char ch = m_sTypeName.charAt (m_nIdx);
- if (Character.isJavaIdentifierStart (ch) || Character.isJavaIdentifierPart (ch) || ch == '.')
+ if (Character.isJavaIdentifierStart (ch) || Character.isJavaIdentifierPart (ch) || ch == '.') {
m_nIdx++;
- else
+ } else {
break;
+ }
}
final AbstractJClass aClazz = ref (m_sTypeName.substring (nStart, m_nIdx));
@@ -1149,8 +1167,9 @@ private AbstractJClass _parseSuffix (@Nonnull final AbstractJClass aClazz)
final char ch = m_sTypeName.charAt (m_nIdx);
- if (ch == '<')
+ if (ch == '<') {
return _parseSuffix (_parseArguments (aClazz));
+ }
if (ch == '[')
{
@@ -1170,8 +1189,9 @@ private AbstractJClass _parseSuffix (@Nonnull final AbstractJClass aClazz)
*/
private void _skipWs ()
{
- while (Character.isWhitespace (m_sTypeName.charAt (m_nIdx)) && m_nIdx < m_sTypeName.length ())
+ while (Character.isWhitespace (m_sTypeName.charAt (m_nIdx)) && m_nIdx < m_sTypeName.length ()) {
m_nIdx++;
+ }
}
/**
@@ -1190,14 +1210,17 @@ private AbstractJClass _parseArguments (@Nonnull final AbstractJClass aRawType)
while (true)
{
args.add (parseTypeName ());
- if (m_nIdx == m_sTypeName.length ())
+ if (m_nIdx == m_sTypeName.length ()) {
throw new IllegalArgumentException ("Missing '>' in " + m_sTypeName);
+ }
final char ch = m_sTypeName.charAt (m_nIdx);
- if (ch == '>')
+ if (ch == '>') {
return aRawType.narrow (args);
+ }
- if (ch != ',')
+ if (ch != ',') {
throw new IllegalArgumentException (m_sTypeName);
+ }
m_nIdx++;
}
}
@@ -1242,4 +1265,25 @@ public Set getAllDontImportClasses ()
{
return new HashSet <> (m_aDontImportClasses);
}
+
+ private final HashMap, AJCodePreprocessor> m_preprocessors = new HashMap<>();
+
+ public T processor(Class processorClass) {
+ @SuppressWarnings("unchecked")
+ T ret = (T) m_preprocessors.get(processorClass);
+ if (ret == null) {
+ try {
+ ret = processorClass.getConstructor().newInstance();
+ m_preprocessors.put(processorClass, ret);
+ } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException
+ | NoSuchMethodException | SecurityException e) {
+ throw new UnsupportedOperationException("catch this", e);
+ }
+ }
+ return ret;
+ }
+
+ public Collection getProcessors() {
+ return Collections.unmodifiableCollection(m_preprocessors.values());
+ }
}
diff --git a/src/main/java/com/helger/jcodemodel/preprocess/AJCodePreprocessor.java b/src/main/java/com/helger/jcodemodel/preprocess/AJCodePreprocessor.java
new file mode 100644
index 00000000..05614568
--- /dev/null
+++ b/src/main/java/com/helger/jcodemodel/preprocess/AJCodePreprocessor.java
@@ -0,0 +1,49 @@
+package com.helger.jcodemodel.preprocess;
+
+import com.helger.jcodemodel.JCodeModel;
+
+/**
+ * A preprocessor adds data in a JCodeModel before it is built.
+ *
+ * This is typically usefull for, but not limited to:
+ *
+ *
+ *
+ *
+ * the typical use case is
+ *
+ * - During JCM modeling, the user requests the preprocessor with
+ * JCM::preprocessor(preprocessorclass). This preprocessor instance is unique in
+ * the JCM for that preprocessor class.
+ * - the preprocessor is used during the JCM modeling, eg by tagging classes,
+ * fields, packages, etc with it eg myprocessor.add(myJCMClass)
+ * - when the user wants to build the JCM, the JCM calls each preprocessor
+ * that has been requested, which can then modify the JCM
+ * - if several processors are requested and at least one modified the JCM
+ * when applied, each processor is applied again, until none generates a
+ * modification anymore
+ *
+ *
+ *
+ *
+ * A preprocessor class must be have an unparametrized constructor. The settings
+ * are set after initialization.
+ *
+ *
+ * @author glelouet
+ *
+ */
+public abstract class AJCodePreprocessor {
+
+ /**
+ *
+ * @param jcm the {@link JCodeModel} we want to apply the processor onto.
+ * @param firstPass
+ * true when the processor has not bee applied to the jcm already.
+ * Typically that means them odifications the processor wanted to do
+ * have already been applied, by itself.
+ * @return true if the application of the processor modified the jcm.
+ */
+ public abstract boolean apply(JCodeModel jcm, boolean firstPass);
+
+}
diff --git a/src/main/java/com/helger/jcodemodel/preprocess/AuthorProcessor.java b/src/main/java/com/helger/jcodemodel/preprocess/AuthorProcessor.java
new file mode 100644
index 00000000..39234636
--- /dev/null
+++ b/src/main/java/com/helger/jcodemodel/preprocess/AuthorProcessor.java
@@ -0,0 +1,88 @@
+package com.helger.jcodemodel.preprocess;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+
+import com.helger.jcodemodel.JCodeModel;
+import com.helger.jcodemodel.JDefinedClass;
+import com.helger.jcodemodel.JDocComment;
+import com.helger.jcodemodel.JPackage;
+
+/**
+ * add a list of author entries to the javadoc of the first-level classes. This
+ * makes tests on authors presence based on equality, so if an existing author
+ * "John Theauthor" is already present, and "john theauthor" is to be added,
+ * both will be present as authors.
+ *
+ * @author gleleout
+ *
+ */
+public class AuthorProcessor extends AJCodePreprocessor {
+
+ private final Set authors = new LinkedHashSet<>();
+
+ /**
+ * add a list of authors
+ *
+ * @param newAuthors
+ * the new authors String to add.
+ */
+ public void add(String... newAuthors) {
+ if (newAuthors == null || newAuthors.length == 0) {
+ return;
+ }
+ authors.addAll(Arrays.asList(newAuthors));
+ }
+
+ @Override
+ public boolean apply(JCodeModel jcm, boolean firstPass) {
+ if (authors.isEmpty()) {
+ return false;
+ }
+ boolean modification = false;
+ for (JPackage pck : jcm.getAllPackages()) {
+ for (JDefinedClass cl : pck.classes()) {
+ if (applyClass(cl)) {
+ modification = true;
+ }
+ }
+ }
+ return modification;
+ }
+
+ protected boolean applyClass(JDefinedClass cl) {
+ Set classAuthors = new LinkedHashSet<>();
+ for (Object subpart : cl.javadoc().getTag(JDocComment.TAG_AUTHOR)) {
+ if (subpart instanceof String) {
+ addAuthors(classAuthors, (String) subpart);
+ }
+ }
+ for (Object javadocPart : cl.javadoc()) {
+ if (javadocPart instanceof String) {
+ String s_part = (String) javadocPart;
+ if (s_part.startsWith("@" + JDocComment.TAG_AUTHOR)) {
+ addAuthors(classAuthors, s_part.substring(JDocComment.TAG_AUTHOR.length() + 1));
+ }
+ }
+ }
+ List missingAuthors = new ArrayList<>(authors);
+ missingAuthors.removeAll(classAuthors);
+ for (String missingAuthor : missingAuthors) {
+ cl.javadoc().append("@" + JDocComment.TAG_AUTHOR + " " + missingAuthor + "\n");
+ }
+ // TODO should be debug
+ // System.err.println("class=" + cl.fullName() + " authors=" + classAuthors
+ // + " missing=" + missingAuthors);
+ return !missingAuthors.isEmpty();
+ }
+
+ protected void addAuthors(Set classAuthors, String authorPart) {
+ for (String splitPart : authorPart.split("[,;]")) {
+ classAuthors.add(splitPart.trim());
+ }
+ }
+
+}
diff --git a/src/main/java/com/helger/jcodemodel/preprocess/GeneratedProcessor.java b/src/main/java/com/helger/jcodemodel/preprocess/GeneratedProcessor.java
new file mode 100644
index 00000000..d2133b1c
--- /dev/null
+++ b/src/main/java/com/helger/jcodemodel/preprocess/GeneratedProcessor.java
@@ -0,0 +1,126 @@
+package com.helger.jcodemodel.preprocess;
+
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
+
+import javax.annotation.processing.Generated;
+
+import com.helger.jcodemodel.AbstractJClass;
+import com.helger.jcodemodel.JAnnotationUse;
+import com.helger.jcodemodel.JCodeModel;
+import com.helger.jcodemodel.JDefinedClass;
+import com.helger.jcodemodel.JPackage;
+
+/**
+ * add a generated annotation on the first-level classes that are not annotated
+ * with {@link Generated}. The {@link #withGenerator(Class) generator class}
+ * must be set, or the processor will skip the codemodel.
+ *
+ * @see https://docs.oracle.com/javase/10/docs/api/javax/annotation/Generated.html
+ * @author glelouet
+ *
+ */
+public class GeneratedProcessor extends AJCodePreprocessor {
+
+ @Override
+ public boolean apply(JCodeModel jcm, boolean firstPass) {
+ if (generator == null) {
+ return false;
+ }
+ String annotationValue = generator.getName();
+ String annotationDate = null;
+ if (addDate) {
+ annotationDate=DateTimeFormatter.ISO_DATE_TIME.format(LocalDateTime.now());
+ }
+ AbstractJClass generatedRef = jcm.ref(Generated.class);
+ boolean modification = false;
+ for (JPackage pck : jcm.getAllPackages()) {
+ for (JDefinedClass cl : pck.classes()) {
+ if (applyClass(cl, annotationValue, annotationDate, comments, generatedRef)) {
+ modification = true;
+ }
+ }
+ }
+ return modification;
+ }
+
+ /**
+ * process a class
+ *
+ * @param cl
+ * the generated class to process
+ * @param annotationValue
+ * the generator used, required.
+ * @param annotationDate
+ * current date in the ISO-8601 format, optional.
+ * @param annotationComments
+ * the comment to add, optional.
+ * @return true if the application did change the code model.
+ */
+ protected boolean applyClass(JDefinedClass cl, String annotationValue, String annotationDate,
+ String annotationComments, AbstractJClass generatedRef) {
+ for (JAnnotationUse ann : cl.annotations()) {
+ AbstractJClass annClass = ann.getAnnotationClass();
+ if (annClass.equals(generatedRef)) {
+ return false;
+ }
+ }
+ JAnnotationUse annotation = cl.annotate(generatedRef);
+ if (annotationDate == null && annotationComments == null) {
+ annotation.param(annotationValue);
+ } else {
+ annotation.param("value", annotationValue);
+ if (annotationDate != null) {
+ annotation.param("date", annotationDate);
+ }
+ if (annotationComments != null) {
+ annotation.param("comments", annotationComments);
+ }
+ }
+ return true;
+ }
+
+ private String comments = null;
+
+ /**
+ * set the comment of the generated annotation.
+ *
+ * @param comment
+ * the comment to be added, can be nul.
+ * @return this.
+ */
+ public GeneratedProcessor withComment(String comment) {
+ comments = comment;
+ return this;
+ }
+
+ private Class> generator = null;
+
+ /**
+ * set the object that generated the code.
+ *
+ * @param generator
+ * the class that generated the code. If none set, or null, the
+ * generator is disabled.
+ * @return this.
+ */
+ public GeneratedProcessor withGenerator(Class> generator) {
+ this.generator = generator;
+ return this;
+ }
+
+ private boolean addDate = true;
+
+ /**
+ * set to add the date in the annotation(default is true)
+ *
+ * @param addDate
+ * the new value
+ * @return this
+ */
+ public GeneratedProcessor withAddDate(boolean addDate) {
+ this.addDate = addDate;
+ return this;
+ }
+
+}
diff --git a/src/main/java/com/helger/jcodemodel/writer/JCMWriter.java b/src/main/java/com/helger/jcodemodel/writer/JCMWriter.java
index 12537c60..302c08bb 100644
--- a/src/main/java/com/helger/jcodemodel/writer/JCMWriter.java
+++ b/src/main/java/com/helger/jcodemodel/writer/JCMWriter.java
@@ -61,6 +61,7 @@
import com.helger.jcodemodel.JResourceDir;
import com.helger.jcodemodel.SourcePrintWriter;
import com.helger.jcodemodel.fmt.AbstractJResourceFile;
+import com.helger.jcodemodel.preprocess.AJCodePreprocessor;
import com.helger.jcodemodel.util.JCValueEnforcer;
import com.helger.jcodemodel.writer.ProgressCodeWriter.IProgressTracker;
@@ -94,8 +95,9 @@ public static String getDefaultNewLine ()
}
// Fall back
- if (ret == null || ret.length () == 0)
+ if (ret == null || ret.length () == 0) {
ret = s_sDefaultNewLine = "\n";
+ }
}
return ret;
}
@@ -214,8 +216,8 @@ public void build (@Nonnull final File aDestDir, @Nullable final IProgressTracke
* stream.
*/
public void build (@Nonnull final File aSrcDir,
- @Nonnull final File aResourceDir,
- @Nullable final IProgressTracker aStatusPT) throws IOException
+ @Nonnull final File aResourceDir,
+ @Nullable final IProgressTracker aStatusPT) throws IOException
{
AbstractCodeWriter aSrcWriter = new FileCodeWriter (aSrcDir, m_aCharset, m_sNewLine);
AbstractCodeWriter aResWriter = new FileCodeWriter (aResourceDir, m_aCharset, m_sNewLine);
@@ -279,19 +281,22 @@ public void build (@Nonnull final AbstractCodeWriter aWriter) throws IOException
* on IO error
*/
public void build (@Nonnull final AbstractCodeWriter aSourceWriter,
- @Nonnull final AbstractCodeWriter aResourceWriter) throws IOException
+ @Nonnull final AbstractCodeWriter aResourceWriter) throws IOException
{
+ preprocess();
try
{
// Copy to avoid concurrent modification exception
final List aPackages = m_aCM.getAllPackages ();
- for (final JPackage aPackage : aPackages)
+ for (final JPackage aPackage : aPackages) {
buildPackage (aSourceWriter, aResourceWriter, aPackage);
+ }
// Write resources only
final List aResourceDirs = m_aCM.getAllResourceDirs ();
- for (final JResourceDir aResourceDir : aResourceDirs)
+ for (final JResourceDir aResourceDir : aResourceDirs) {
buildResourceDir (aResourceWriter, aResourceDir);
+ }
}
finally
{
@@ -300,10 +305,28 @@ public void build (@Nonnull final AbstractCodeWriter aSourceWriter,
}
}
+ private void preprocess() {
+ if (m_aCM.getProcessors().isEmpty()) {
+ return;
+ }
+ int passes = 0;
+ boolean modif = false;
+ do {
+ modif = false;
+ for (AJCodePreprocessor processor : m_aCM.getProcessors()) {
+ if (processor.apply(m_aCM, passes == 0)) {
+ modif = true;
+ }
+ }
+ passes++;
+ } while (modif);
+ // log processors applied with number of passes.
+ }
+
@Nonnull
private JFormatter _createJavaSourceFileWriter (@Nonnull final AbstractCodeWriter aSrcWriter,
- @Nonnull final JPackage aPackage,
- @Nonnull final String sClassFilename) throws IOException
+ @Nonnull final JPackage aPackage,
+ @Nonnull final String sClassFilename) throws IOException
{
final SourcePrintWriter aWriter = aSrcWriter.openSource (aPackage, sClassFilename);
final JFormatter ret = new JFormatter (aWriter, m_sIndentString);
@@ -314,8 +337,8 @@ private JFormatter _createJavaSourceFileWriter (@Nonnull final AbstractCodeWrite
@SuppressWarnings ("deprecation")
public void buildPackage (@Nonnull final AbstractCodeWriter aSrcWriter,
- @Nonnull final AbstractCodeWriter aResWriter,
- @Nonnull final JPackage aPackage) throws IOException
+ @Nonnull final AbstractCodeWriter aResWriter,
+ @Nonnull final JPackage aPackage) throws IOException
{
// write classes
for (final JDefinedClass c : aPackage.classes ())
@@ -339,12 +362,14 @@ public void buildPackage (@Nonnull final AbstractCodeWriter aSrcWriter,
{
try (final IJFormatter f = _createJavaSourceFileWriter (aSrcWriter, aPackage, "package-info.java"))
{
- if (!aJavaDoc.isEmpty ())
+ if (!aJavaDoc.isEmpty ()) {
f.generable (aJavaDoc);
+ }
// TODO: think about importing
- for (final JAnnotationUse a : aAnnotations)
+ for (final JAnnotationUse a : aAnnotations) {
f.generable (a).newline ();
+ }
f.declaration (aPackage);
}
@@ -355,7 +380,7 @@ public void buildPackage (@Nonnull final AbstractCodeWriter aSrcWriter,
{
final AbstractCodeWriter cw = rsrc.isResource () ? aResWriter : aSrcWriter;
try (final OutputStream os = cw.openBinary (aPackage, rsrc.name ());
- final OutputStream bos = new BufferedOutputStream (os))
+ final OutputStream bos = new BufferedOutputStream (os))
{
rsrc.build (bos);
}
@@ -363,13 +388,13 @@ public void buildPackage (@Nonnull final AbstractCodeWriter aSrcWriter,
}
public void buildResourceDir (@Nonnull final AbstractCodeWriter aResWriter,
- @Nonnull final JResourceDir aResourceDir) throws IOException
+ @Nonnull final JResourceDir aResourceDir) throws IOException
{
// write resources
for (final AbstractJResourceFile rsrc : aResourceDir.getAllResourceFiles ())
{
try (final OutputStream os = aResWriter.openBinary (aResourceDir.name (), rsrc.name ());
- final OutputStream bos = new BufferedOutputStream (os))
+ final OutputStream bos = new BufferedOutputStream (os))
{
rsrc.build (bos);
}
diff --git a/src/test/java/com/helger/jcodemodel/preprocess/AuthorPreprocessorTest.java b/src/test/java/com/helger/jcodemodel/preprocess/AuthorPreprocessorTest.java
new file mode 100644
index 00000000..8062f943
--- /dev/null
+++ b/src/test/java/com/helger/jcodemodel/preprocess/AuthorPreprocessorTest.java
@@ -0,0 +1,30 @@
+package com.helger.jcodemodel.preprocess;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.helger.jcodemodel.JCodeModel;
+import com.helger.jcodemodel.JCodeModelException;
+import com.helger.jcodemodel.JDefinedClass;
+import com.helger.jcodemodel.JDocComment;
+import com.helger.jcodemodel.util.EFileSystemConvention;
+
+public class AuthorPreprocessorTest {
+
+ @Test
+ public void testAddAuthor() throws JCodeModelException, IOException {
+ JCodeModel cm = new JCodeModel().setFileSystemConvention(EFileSystemConvention.LINUX);
+ JDefinedClass cl = cm._class("my.Test");
+ cl.javadoc().addTag(JDocComment.TAG_AUTHOR).add("existingAuthor1");
+ cl.javadoc().append("@author author1\n");
+ cl.javadoc().append("@author existingAuthor2\n");
+ AuthorProcessor test = cm.processor(AuthorProcessor.class);
+ test.add("newAuthor1", "existingAuthor1", "newAuthor2", "existingAuthor2");
+ Assert.assertTrue(test.apply(cm, true));
+ Assert.assertFalse(test.apply(cm, true));
+ // TODO more tests with memory compiler
+ }
+
+}
diff --git a/src/test/java/com/helger/jcodemodel/preprocess/GeneratedPreprocessorTest.java b/src/test/java/com/helger/jcodemodel/preprocess/GeneratedPreprocessorTest.java
new file mode 100644
index 00000000..9bab5374
--- /dev/null
+++ b/src/test/java/com/helger/jcodemodel/preprocess/GeneratedPreprocessorTest.java
@@ -0,0 +1,31 @@
+package com.helger.jcodemodel.preprocess;
+
+import java.io.IOException;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.helger.jcodemodel.JAnnotationUse;
+import com.helger.jcodemodel.JCodeModel;
+import com.helger.jcodemodel.JCodeModelException;
+import com.helger.jcodemodel.JDefinedClass;
+import com.helger.jcodemodel.util.EFileSystemConvention;
+
+public class GeneratedPreprocessorTest {
+
+ @Test
+ public void testAddGenerated() throws JCodeModelException, IOException {
+ JCodeModel cm = new JCodeModel().setFileSystemConvention(EFileSystemConvention.LINUX);
+ JDefinedClass cl = cm._class("my.Test");
+ GeneratedProcessor test = cm.processor(GeneratedProcessor.class);
+ test.withGenerator(JCodeModel.class);
+ Assert.assertTrue(test.apply(cm, true));
+ Assert.assertFalse(test.apply(cm, true));
+ JAnnotationUse annotation = cl.annotations().iterator().next();
+ Assert.assertNull(annotation.getAnnotationMembers().get("comments"));
+ Assert.assertNotNull(annotation.getAnnotationMembers().get("date"));
+ // TODO more tests when memory compiler. Otherwise too many casts.
+ // Assert.assertEquals(JCodeModel.class.getSimpleName(),
+ // ((JAnnotationStringValue)annotation.getAnnotationMembers().get("value")).);
+ }
+}