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 + *

    + *
  1. During JCM modeling, the user requests the preprocessor with + * JCM::preprocessor(preprocessorclass). This preprocessor instance is unique in + * the JCM for that preprocessor class.
  2. + *
  3. the preprocessor is used during the JCM modeling, eg by tagging classes, + * fields, packages, etc with it eg myprocessor.add(myJCMClass)
  4. + *
  5. when the user wants to build the JCM, the JCM calls each preprocessor + * that has been requested, which can then modify the JCM
  6. + *
  7. 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
  8. + *
+ *

+ * + *

+ * 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")).); + } +}