diff --git a/src/main/java/com/helger/jcodemodel/AbstractJAnnotationValueOwned.java b/src/main/java/com/helger/jcodemodel/AbstractJAnnotationValueOwned.java index ed4e196a..5507b65b 100644 --- a/src/main/java/com/helger/jcodemodel/AbstractJAnnotationValueOwned.java +++ b/src/main/java/com/helger/jcodemodel/AbstractJAnnotationValueOwned.java @@ -62,10 +62,16 @@ protected JEnumConstantExpr (@Nonnull final Enum aEnumConstant) m_aEnumConstant = ValueEnforcer.notNull (aEnumConstant, "EnumConstant"); } + @Override public void generate (@Nonnull final IJFormatter f) { f.type (owner ().ref (m_aEnumConstant.getDeclaringClass ())).print ('.').print (m_aEnumConstant.name ()); } + + public Enum getEnumConstant () + { + return m_aEnumConstant; + } } protected static final class FullClassNameExpr implements IJExpression @@ -77,6 +83,7 @@ protected FullClassNameExpr (@Nonnull final Class aClass) m_aClass = ValueEnforcer.notNull (aClass, "Class"); } + @Override public void generate (@Nonnull final IJFormatter f) { f.print (JCNameUtilities.getFullName (m_aClass)).print (".class"); diff --git a/src/main/java/com/helger/jcodemodel/JCodeModel.java b/src/main/java/com/helger/jcodemodel/JCodeModel.java index 60f7f2a0..ec24d236 100644 --- a/src/main/java/com/helger/jcodemodel/JCodeModel.java +++ b/src/main/java/com/helger/jcodemodel/JCodeModel.java @@ -40,6 +40,11 @@ */ package com.helger.jcodemodel; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; @@ -223,7 +228,7 @@ public final IFileSystemConvention getFileSystemConvention () */ @Nonnull public final IFileSystemConvention setFileSystemConvention (@Nonnull final IFileSystemConvention aFSConvention) throws JCaseSensitivityChangeException, - JInvalidFileNameException + JInvalidFileNameException { ValueEnforcer.notNull (aFSConvention, "FSConvention"); if (aFSConvention == m_aFSConvention) @@ -464,15 +469,15 @@ 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) return rootPackage ()._class (nMods, sFullyQualifiedClassName, eClassType); return _package (sFullyQualifiedClassName.substring (0, nIdx))._class (nMods, - sFullyQualifiedClassName.substring (nIdx + 1), - eClassType); + sFullyQualifiedClassName.substring (nIdx + 1), + eClassType); } /** @@ -520,7 +525,7 @@ public JDefinedClass _class (final int nMods, @Nonnull final String sFullyQualif */ @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); } @@ -769,7 +774,7 @@ public AbstractJClass ref (@Nonnull final Class aClazz) */ @Nonnull public JDefinedClass ref (@Nonnull final TypeElement aElement, @Nonnull final Elements aElementUtils) throws ErrorTypeFound, - CodeModelBuildingException + CodeModelBuildingException { final JCodeModelJavaxLangModelAdapter adapter = new JCodeModelJavaxLangModelAdapter (this, aElementUtils); return adapter.getClass (aElement); @@ -806,7 +811,7 @@ public JDefinedClass ref (@Nonnull final TypeElement aElement, @Nonnull final El */ @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); @@ -1083,4 +1088,36 @@ public Set getAllDontImportClasses () { return new HashSet <> (m_aDontImportClasses); } + + /** + * copy a codemodel using serialization. + * + * @param source + * codemodel to copy + * @return a deserialization of the serialization of the source. + */ + public static JCodeModel copySerial (JCodeModel source) + { + try + { + ByteArrayOutputStream buffer = new ByteArrayOutputStream (); + new ObjectOutputStream (buffer).writeObject (source); + ByteArrayInputStream in = new ByteArrayInputStream (buffer.toByteArray ()); + return (JCodeModel) new ObjectInputStream (in).readObject (); + } + catch (IOException | ClassNotFoundException e) + { + throw new UnsupportedOperationException ("catch this", e); + } + } + + /** + * create a new copy of this model + * + * @return a new object, which should have the same representation but not linked to this in any way. + */ + public JCodeModel copy () + { + return copySerial (this); + } } diff --git a/src/main/java/com/helger/jcodemodel/JReferencedClass.java b/src/main/java/com/helger/jcodemodel/JReferencedClass.java index 6c25813d..08c2dc36 100644 --- a/src/main/java/com/helger/jcodemodel/JReferencedClass.java +++ b/src/main/java/com/helger/jcodemodel/JReferencedClass.java @@ -76,6 +76,11 @@ class JReferencedClass extends AbstractJClass implements IJDeclaration assert !m_aClass.isArray (); } + public Class getReferencedClass () + { + return m_aClass; + } + @Override public String name () { @@ -142,17 +147,20 @@ public Iterator _implements () { private int m_nIdx = 0; + @Override public boolean hasNext () { return m_nIdx < aInterfaces.length; } + @Override @Nonnull public AbstractJClass next () { return owner ().ref (aInterfaces[m_nIdx++]); } + @Override public void remove () { throw new UnsupportedOperationException (); @@ -189,6 +197,7 @@ public final JPrimitiveType getPrimitiveType () return m_aPrimitiveType; } + @Override public void declare (final IJFormatter f) { // Nothing to do here... @@ -203,7 +212,7 @@ public void declare (final IJFormatter f) @Override protected AbstractJClass substituteParams (@Nonnull final JTypeVar [] aVariables, - @Nonnull final List aBindings) + @Nonnull final List aBindings) { // TODO: does JDK 1.5 reflection provides these information? return this; diff --git a/src/main/java/com/helger/jcodemodel/JResourceDir.java b/src/main/java/com/helger/jcodemodel/JResourceDir.java index 35b9b541..be38b9fc 100644 --- a/src/main/java/com/helger/jcodemodel/JResourceDir.java +++ b/src/main/java/com/helger/jcodemodel/JResourceDir.java @@ -41,6 +41,7 @@ package com.helger.jcodemodel; import java.io.File; +import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; import java.util.List; @@ -66,7 +67,7 @@ * * @since 3.3.1 */ -public class JResourceDir implements IJOwned +public class JResourceDir implements IJOwned, Serializable { public static final char SEPARATOR = FilenameHelper.UNIX_SEPARATOR; public static final String SEPARATOR_STR = Character.toString (SEPARATOR); @@ -105,8 +106,8 @@ public class JResourceDir implements IJOwned * If a part of the package name is not a valid filename part. */ protected JResourceDir (@Nonnull final JCodeModel aOwner, - @Nullable final JResourceDir aParentDir, - @Nonnull final String sName) throws JInvalidFileNameException + @Nullable final JResourceDir aParentDir, + @Nonnull final String sName) throws JInvalidFileNameException { ValueEnforcer.notNull (sName, "Name"); ValueEnforcer.notNull (aOwner, "CodeModel"); diff --git a/src/main/java/com/helger/jcodemodel/JTypeVarClass.java b/src/main/java/com/helger/jcodemodel/JTypeVarClass.java index 16468ea8..01d96284 100644 --- a/src/main/java/com/helger/jcodemodel/JTypeVarClass.java +++ b/src/main/java/com/helger/jcodemodel/JTypeVarClass.java @@ -59,6 +59,11 @@ protected JTypeVarClass (@Nonnull final AbstractJClass aClass) m_aClass = aClass; } + public AbstractJClass getRefClass () + { + return m_aClass; + } + @Override @Nonnull public String name () @@ -68,10 +73,8 @@ public String name () { final JTypeVar [] aTypeParams = ((JDefinedClass) m_aClass).typeParams (); if (aTypeParams.length > 0) - { // We need the type params here! return new JNarrowedClass (m_aClass, aTypeParams).name (); - } } return m_aClass.name (); } @@ -82,10 +85,8 @@ public String fullName () { // This method is e.g. used for import statements if (m_aClass instanceof JNarrowedClass) - { // Avoid the type parameters return ((JNarrowedClass) m_aClass).erasure ().fullName (); - } return m_aClass.fullName (); } diff --git a/src/main/java/com/helger/jcodemodel/JVar.java b/src/main/java/com/helger/jcodemodel/JVar.java index 23c74417..501efbf8 100644 --- a/src/main/java/com/helger/jcodemodel/JVar.java +++ b/src/main/java/com/helger/jcodemodel/JVar.java @@ -95,9 +95,9 @@ public class JVar implements IJAssignmentTarget, IJDeclaration, IJAnnotatable * Value to initialize this variable to */ public JVar (@Nonnull final JMods aMods, - @Nonnull final AbstractJType aType, - @Nonnull final String sName, - @Nullable final IJExpression aInitExpr) + @Nonnull final AbstractJType aType, + @Nonnull final String sName, + @Nullable final IJExpression aInitExpr) { ValueEnforcer.isTrue (JJavaName.isJavaIdentifier (sName), () -> "Illegal variable name '" + sName + "'"); m_aMods = aMods; @@ -189,6 +189,11 @@ public AbstractJType type (@Nonnull final AbstractJType aNewType) return aOldType; } + public List getAnnotations () + { + return m_aAnnotations == null ? Collections.emptyList () : m_aAnnotations; + } + /** * Adds an annotation to this variable. * @@ -196,6 +201,7 @@ public AbstractJType type (@Nonnull final AbstractJType aNewType) * The annotation class to annotate the field with * @return New {@link JAnnotationUse} */ + @Override @Nonnull public JAnnotationUse annotate (@Nonnull final AbstractJClass aClazz) { @@ -213,6 +219,7 @@ public JAnnotationUse annotate (@Nonnull final AbstractJClass aClazz) * The annotation class to annotate the field with * @return New {@link JAnnotationUse} */ + @Override @Nonnull public JAnnotationUse annotate (@Nonnull final Class aClazz) { @@ -227,6 +234,7 @@ public List annotationsMutable () return m_aAnnotations; } + @Override @Nonnull public List annotations () { @@ -257,11 +265,13 @@ public void bind (@Nonnull final IJFormatter f) f.print ('=').generable (m_aInitExpr); } + @Override public void declare (@Nonnull final IJFormatter f) { f.var (this).print (';').newline (); } + @Override public void generate (@Nonnull final IJFormatter f) { f.id (m_sName); diff --git a/src/main/java/com/helger/jcodemodel/util/ClassNameComparator.java b/src/main/java/com/helger/jcodemodel/util/ClassNameComparator.java index 6627d689..2e96b617 100644 --- a/src/main/java/com/helger/jcodemodel/util/ClassNameComparator.java +++ b/src/main/java/com/helger/jcodemodel/util/ClassNameComparator.java @@ -40,6 +40,7 @@ */ package com.helger.jcodemodel.util; +import java.io.Serializable; import java.util.Comparator; import javax.annotation.Nonnull; @@ -52,7 +53,7 @@ * * @author Kohsuke Kawaguchi (kohsuke.kawaguchi@sun.com) */ -public final class ClassNameComparator implements Comparator +public final class ClassNameComparator implements Comparator , Serializable { private static final ClassNameComparator s_aInstance = new ClassNameComparator (); @@ -70,6 +71,7 @@ public static ClassNameComparator getInstance () * to packages java and javax over all others. This method is used to sort * generated import statements in a conventional way for readability. */ + @Override public int compare (@Nonnull final AbstractJClass aObj1, @Nonnull final AbstractJClass aObj2) { if (aObj1.isError () && aObj2.isError ()) diff --git a/src/main/java/com/helger/jcodemodel/util/FSName.java b/src/main/java/com/helger/jcodemodel/util/FSName.java index 51315b43..1f255397 100644 --- a/src/main/java/com/helger/jcodemodel/util/FSName.java +++ b/src/main/java/com/helger/jcodemodel/util/FSName.java @@ -40,6 +40,7 @@ */ package com.helger.jcodemodel.util; +import java.io.Serializable; import java.util.Locale; import javax.annotation.Nonnull; @@ -55,7 +56,7 @@ * @author Philip Helger * @since 3.4.0 */ -public final class FSName implements Comparable +public final class FSName implements Comparable , Serializable { private final String m_sName; private final String m_sKey; @@ -94,6 +95,7 @@ public int hashCode () return ret; } + @Override public int compareTo (@Nonnull final FSName o) { return m_sKey.compareTo (o.m_sKey); diff --git a/src/main/java/com/helger/jcodemodel/writer/StringCodeWriter.java b/src/main/java/com/helger/jcodemodel/writer/StringCodeWriter.java new file mode 100644 index 00000000..1f2c3a31 --- /dev/null +++ b/src/main/java/com/helger/jcodemodel/writer/StringCodeWriter.java @@ -0,0 +1,85 @@ +package com.helger.jcodemodel.writer; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Map.Entry; +import java.util.stream.Collectors; + +import com.helger.jcodemodel.JCodeModel; + +/** + * + * CodeWriter that stores {@link OutputStream}s for the files. A call to toString() will then sort the files and append + * their + * content with a leading line containing the name of the file. + * + * @author glelouet + * + */ +public class StringCodeWriter extends AbstractCodeWriter +{ + + public StringCodeWriter (Charset aEncoding, String sNewLine) + { + super (aEncoding, sNewLine); + } + + private HashMap binaries = new HashMap <> (); + + @Override + public OutputStream openBinary (String sDirName, String sFilename) throws IOException + { + return binaries.computeIfAbsent (sDirName + "/" + sFilename, + o -> new ByteArrayOutputStream ()); + } + + @Override + public void close () throws IOException + { + } + + public String getString () + { + ArrayList > coll = new ArrayList <> (binaries.entrySet ()); + Collections.sort (coll, Comparator.comparing (Entry::getKey)); + return "model:" + getNewLine () + + coll.stream ().map (e -> e.getKey () + getNewLine () + e.getValue ().toString (encoding ())) + .collect (Collectors.joining (getNewLine ())); + } + + @Override + public String toString () + { + return getString (); + } + + /** + * transform a {@link JCodeModel} into a {@link String} using this class. + * + * @param target + * the codemodel to export + * @return the representation fo the codemodel. + */ + public static String represent (JCodeModel target) + { + StringCodeWriter scw = new StringCodeWriter (StandardCharsets.UTF_8, "\n"); + try + { + new JCMWriter (target).build (scw); + } + catch (IOException e) + { + throw new UnsupportedOperationException ("catch this", e); + } + String ret = scw.getString (); + return ret; + } + +} diff --git a/src/test/java/com/helger/jcodemodel/ModelCopyTest.java b/src/test/java/com/helger/jcodemodel/ModelCopyTest.java new file mode 100644 index 00000000..431d258e --- /dev/null +++ b/src/test/java/com/helger/jcodemodel/ModelCopyTest.java @@ -0,0 +1,116 @@ +package com.helger.jcodemodel; + +import java.util.Arrays; +import java.util.Collection; +import java.util.function.Consumer; + +import org.junit.Assert; +import org.junit.Test; + +import com.helger.jcodemodel.exceptions.JCodeModelException; +import com.helger.jcodemodel.writer.StringCodeWriter; + +/** + * abstract method for a test of copying. Such a test consist in creating a source codemodel, copying it, check the + * equality of the representation of the copy and the source ; then apply several modifications on the source, each + * result in a model varying from the copy ; then apply those modifications to the copy, which in the end should have + * the same representation as the source.
+ * + * This class defines + *
    + *
  • {@link #execute()} that should be called in the test,
  • + *
  • {@link #createCM()} that creates a base code model,
  • + *
  • {@link #copy(JCodeModel)} that defines how to copy a {@link JCodeModel},
  • + *
  • {@link #represent(JCodeModel)} to make a representation of a codemodel,
  • + *
  • {@link #modifications()} that lists a series of modifications to apply.
  • + *
+ * + * @author glelouet + */ +public class ModelCopyTest +{ + + @Test + public void execute () + { + JCodeModel cm = createCM (); + JCodeModel copy = copy (cm); + Assert.assertEquals (represent (cm), represent (copy)); + for (Consumer m : modifications ()) + { + m.accept (cm); + Assert.assertNotEquals (represent (cm), represent (copy)); + } + for (Consumer m : modifications ()) + m.accept (copy); + Assert.assertEquals (represent (cm), represent (copy)); + } + + protected JCodeModel createCM () + { + try + { + JCodeModel ret = new JCodeModel (); + + // create an interface IMyClass with method default String string(){return "yes";} + JDefinedClass itf = ret._class ("my.pckg.IMyClass", EClassType.INTERFACE); + JMethod methd = itf.method (JMod.PUBLIC | JMod.DEFAULT, ret.ref (String.class), "string"); + methd.body ()._return (JExpr.lit ("yes")); + + // create an implementation of that interface, for which toString() returns the string(); + JDefinedClass imp = itf._package ()._class (JMod.PUBLIC, "Impl")._implements (itf); + imp.method (JMod.PUBLIC, ret.ref (String.class), "toString").body ()._return (JExpr.invoke (methd)); + return ret; + } + catch (JCodeModelException e) + { + throw new UnsupportedOperationException ("catch this", e); + } + } + + protected JCodeModel copy (JCodeModel source) + { + return source.copy (); + } + + protected String represent (JCodeModel target) + { + return StringCodeWriter.represent (target); + } + + public Collection > modifications () + { + return Arrays.asList (cm -> + { + try + { + cm._class (JMod.PUBLIC, "MyClass"); + } + catch (JCodeModelException e) + { + throw new UnsupportedOperationException ("catch this", e); + } + }, cm -> + { + JDefinedClass cl = cm._getClass ("my.pckg.IMyClass"); + cl.method (JMod.DEFAULT, cm.BOOLEAN, "yes").body ()._return (JExpr.TRUE); + }, cm -> + { + try + { + JDefinedClass cl = cm._getClass ("my.pckg.IMyClass"); + JDefinedClass newcl = cl._class (JMod.STATIC | JMod.PUBLIC, "InternalIntClass"); + JFieldVar fld = newcl.field (JMod.PRIVATE, cm.INT, "intField"); + JMethod cons = newcl.constructor (JMod.PUBLIC); + JVar consParam = cons.param (cm.INT, "number"); + cons.body ().assign (fld, consParam); + newcl.method (JMod.PUBLIC, cm.INT, "incr").body ()._return (JExpr.preincr (fld)); + } + catch (JCodeModelException e) + { + throw new UnsupportedOperationException ("catch this", e); + } + }); + } + +}