Create, get, modify and remove comments, support operating paragraphs, pictures and tables in comments

git-svn-id: https://svn.apache.org/repos/asf/poi/trunk@1887867 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
index 39e4758..ebf6a4d 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/BodyType.java
@@ -26,5 +26,6 @@
     HEADER,
     FOOTER,
     FOOTNOTE,
-    TABLECELL
+    TABLECELL,
+    COMMENT
 }
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
index 0df8111..f9ee63f 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComment.java
@@ -16,43 +16,411 @@
 ==================================================================== */
 package org.apache.poi.xwpf.usermodel;
 
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment;
-import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTP;
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.xmlbeans.XmlCursor;
+import org.apache.xmlbeans.XmlObject;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.*;
+
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Collections;
+import java.util.List;
 
 /**
  * Sketch of XWPF comment class
- *
- * @author Yury Batrakov (batrakov at gmail.com)
  */
-public class XWPFComment {
-    protected String id;
-    protected String author;
-    protected StringBuilder text;
+public class XWPFComment implements IBody {
 
-    public XWPFComment(CTComment comment, XWPFDocument document) {
-        text = new StringBuilder(64);
-        id = comment.getId().toString();
-        author = comment.getAuthor();
+    protected CTComment ctComment;
+    protected XWPFComments comments;
+    protected XWPFDocument document;
+    private List<XWPFParagraph> paragraphs = new ArrayList<>();
+    private List<XWPFTable> tables = new ArrayList<>();
+    private List<IBodyElement> bodyElements = new ArrayList<>();
 
-        for (CTP ctp : comment.getPArray()) {
-            if(text.length() > 0) {
-                text.append("\n");
+    public XWPFComment(CTComment ctComment, XWPFComments comments) {
+        this.comments = comments;
+        this.ctComment = ctComment;
+        this.document = comments.getXWPFDocument();
+        init();
+    }
+
+    protected void init() {
+        XmlCursor cursor = ctComment.newCursor();
+        cursor.selectPath("./*");
+        while (cursor.toNextSelection()) {
+            XmlObject o = cursor.getObject();
+            if (o instanceof CTP) {
+                XWPFParagraph p = new XWPFParagraph((CTP) o, this);
+                bodyElements.add(p);
+                paragraphs.add(p);
+            } else if (o instanceof CTTbl) {
+                XWPFTable t = new XWPFTable((CTTbl) o, this);
+                bodyElements.add(t);
+                tables.add(t);
+            } else if (o instanceof CTSdtBlock) {
+                XWPFSDT c = new XWPFSDT((CTSdtBlock) o, this);
+                bodyElements.add(c);
             }
 
-            XWPFParagraph p = new XWPFParagraph(ctp, document);
-            text.append(p.getText());
         }
+        cursor.dispose();
     }
 
-    public String getId() {
-        return id;
+    /**
+     * Get the Part to which the comment belongs, which you need for adding
+     * relationships to other parts
+     *
+     * @return {@link POIXMLDocumentPart} that contains the comment.
+     * @see org.apache.poi.xwpf.usermodel.IBody#getPart()
+     */
+    @Override
+    public POIXMLDocumentPart getPart() {
+        return comments;
     }
 
-    public String getAuthor() {
-        return author;
+    /**
+     * Get the part type {@link BodyType} of the comment.
+     *
+     * @return The {@link BodyType} value.
+     * @see org.apache.poi.xwpf.usermodel.IBody#getPartType()
+     */
+    @Override
+    public BodyType getPartType() {
+        return BodyType.COMMENT;
+    }
+
+    /**
+     * Gets the body elements ({@link IBodyElement}) of the comment.
+     *
+     * @return List of body elements.
+     */
+    @Override
+    public List<IBodyElement> getBodyElements() {
+        return Collections.unmodifiableList(bodyElements);
+    }
+
+    /**
+     * Returns the paragraph(s) that holds the text of the comment.
+     */
+    @Override
+    public List<XWPFParagraph> getParagraphs() {
+        return Collections.unmodifiableList(paragraphs);
+    }
+
+    /**
+     * Get the list of {@link XWPFTable}s in the comment.
+     *
+     * @return List of tables
+     */
+    @Override
+    public List<XWPFTable> getTables() {
+        return Collections.unmodifiableList(tables);
+    }
+
+    @Override
+    public XWPFParagraph getParagraph(CTP p) {
+        for (XWPFParagraph paragraph : paragraphs) {
+            if (paragraph.getCTP().equals(p))
+                return paragraph;
+        }
+        return null;
+    }
+
+    @Override
+    public XWPFTable getTable(CTTbl ctTable) {
+        for (XWPFTable table : tables) {
+            if (table == null)
+                return null;
+            if (table.getCTTbl().equals(ctTable))
+                return table;
+        }
+        return null;
+    }
+
+    @Override
+    public XWPFParagraph getParagraphArray(int pos) {
+        if (pos >= 0 && pos < paragraphs.size()) {
+            return paragraphs.get(pos);
+        }
+        return null;
+    }
+
+    @Override
+    public XWPFTable getTableArray(int pos) {
+        if (pos >= 0 && pos < tables.size()) {
+            return tables.get(pos);
+        }
+        return null;
+    }
+
+    @Override
+    public XWPFParagraph insertNewParagraph(XmlCursor cursor) {
+        if (isCursorInCmt(cursor)) {
+            String uri = CTP.type.getName().getNamespaceURI();
+            String localPart = "p";
+            cursor.beginElement(localPart, uri);
+            cursor.toParent();
+            CTP p = (CTP) cursor.getObject();
+            XWPFParagraph newP = new XWPFParagraph(p, this);
+            XmlObject o = null;
+            while (!(o instanceof CTP) && (cursor.toPrevSibling())) {
+                o = cursor.getObject();
+            }
+            if ((!(o instanceof CTP)) || o == p) {
+                paragraphs.add(0, newP);
+            } else {
+                int pos = paragraphs.indexOf(getParagraph((CTP) o)) + 1;
+                paragraphs.add(pos, newP);
+            }
+            int i = 0;
+            XmlCursor p2 = p.newCursor();
+            cursor.toCursor(p2);
+            p2.dispose();
+            while (cursor.toPrevSibling()) {
+                o = cursor.getObject();
+                if (o instanceof CTP || o instanceof CTTbl)
+                    i++;
+            }
+            bodyElements.add(i, newP);
+            p2 = p.newCursor();
+            cursor.toCursor(p2);
+            cursor.toEndToken();
+            p2.dispose();
+            return newP;
+        }
+        return null;
+    }
+
+    private boolean isCursorInCmt(XmlCursor cursor) {
+        XmlCursor verify = cursor.newCursor();
+        verify.toParent();
+        boolean result = (verify.getObject() == this.ctComment);
+        verify.dispose();
+        return result;
+    }
+
+    @Override
+    public XWPFTable insertNewTbl(XmlCursor cursor) {
+        if (isCursorInCmt(cursor)) {
+            String uri = CTTbl.type.getName().getNamespaceURI();
+            String localPart = "tbl";
+            cursor.beginElement(localPart, uri);
+            cursor.toParent();
+            CTTbl t = (CTTbl) cursor.getObject();
+            XWPFTable newT = new XWPFTable(t, this);
+            cursor.removeXmlContents();
+            XmlObject o = null;
+            while (!(o instanceof CTTbl) && (cursor.toPrevSibling())) {
+                o = cursor.getObject();
+            }
+            if (!(o instanceof CTTbl)) {
+                tables.add(0, newT);
+            } else {
+                int pos = tables.indexOf(getTable((CTTbl) o)) + 1;
+                tables.add(pos, newT);
+            }
+            int i = 0;
+            XmlCursor cursor2 = t.newCursor();
+            while (cursor2.toPrevSibling()) {
+                o = cursor2.getObject();
+                if (o instanceof CTP || o instanceof CTTbl) {
+                    i++;
+                }
+            }
+            cursor2.dispose();
+            bodyElements.add(i, newT);
+            cursor2 = t.newCursor();
+            cursor.toCursor(cursor2);
+            cursor.toEndToken();
+            cursor2.dispose();
+            return newT;
+        }
+        return null;
+    }
+
+    @Override
+    public void insertTable(int pos, XWPFTable table) {
+        bodyElements.add(pos, table);
+        int i = 0;
+        for (CTTbl tbl : ctComment.getTblList()) {
+            if (tbl == table.getCTTbl()) {
+                break;
+            }
+            i++;
+        }
+        tables.add(i, table);
+
+    }
+
+    @Override
+    public XWPFTableCell getTableCell(CTTc cell) {
+        XmlCursor cursor = cell.newCursor();
+        cursor.toParent();
+        XmlObject o = cursor.getObject();
+        if (!(o instanceof CTRow)) {
+            cursor.dispose();
+            return null;
+        }
+        CTRow row = (CTRow) o;
+        cursor.toParent();
+        o = cursor.getObject();
+        cursor.dispose();
+        if (!(o instanceof CTTbl)) {
+            return null;
+        }
+        CTTbl tbl = (CTTbl) o;
+        XWPFTable table = getTable(tbl);
+        if (table == null) {
+            return null;
+        }
+        XWPFTableRow tableRow = table.getRow(row);
+        return tableRow.getTableCell(cell);
+    }
+
+    /**
+     * Get the {@link XWPFDocument} the comment is part of.
+     *
+     * @see org.apache.poi.xwpf.usermodel.IBody#getXWPFDocument()
+     */
+    @Override
+    public XWPFDocument getXWPFDocument() {
+        return document;
     }
 
     public String getText() {
+        StringBuilder text = new StringBuilder();
+        for (XWPFParagraph p : paragraphs) {
+            if (text.length() > 0) {
+                text.append("\n");
+            }
+            text.append(p.getText());
+        }
         return text.toString();
     }
+
+    public XWPFParagraph createParagraph() {
+        XWPFParagraph paragraph = new XWPFParagraph(ctComment.addNewP(), this);
+        paragraphs.add(paragraph);
+        bodyElements.add(paragraph);
+        return paragraph;
+    }
+
+    public void removeParagraph(XWPFParagraph paragraph) {
+        if (paragraphs.contains(paragraph)) {
+            CTP ctP = paragraph.getCTP();
+            XmlCursor c = ctP.newCursor();
+            c.removeXml();
+            c.dispose();
+            paragraphs.remove(paragraph);
+            bodyElements.remove(paragraph);
+        }
+    }
+
+    public void removeTable(XWPFTable table) {
+        if (tables.contains(table)) {
+            CTTbl ctTbl = table.getCTTbl();
+            XmlCursor c = ctTbl.newCursor();
+            c.removeXml();
+            c.dispose();
+            tables.remove(table);
+            bodyElements.remove(table);
+        }
+    }
+
+    public XWPFTable createTable(int rows, int cols) {
+        XWPFTable table = new XWPFTable(ctComment.addNewTbl(), this, rows,
+                cols);
+        tables.add(table);
+        bodyElements.add(table);
+        return table;
+    }
+
+    /**
+     * Gets the underlying CTComment object for the comment.
+     *
+     * @return CTComment object
+     */
+    public CTComment getCtComment() {
+        return ctComment;
+    }
+
+    /**
+     * The owning object for this comment
+     *
+     * @return The {@link XWPFComments} object that contains this comment.
+     */
+    public XWPFComments getComments() {
+        return comments;
+    }
+
+    /**
+     * Get a unique identifier for the current comment. The restrictions on the
+     * id attribute, if any, are defined by the parent XML element. If this
+     * attribute is omitted, then the document is non-conformant.
+     *
+     * @return string id
+     */
+    public String getId() {
+        return ctComment.getId().toString();
+    }
+
+    /**
+     * Get the author of the current comment
+     *
+     * @return author of the current comment
+     */
+    public String getAuthor() {
+        return ctComment.getAuthor();
+    }
+
+    /**
+     * Specifies the author for the current comment If this attribute is
+     * omitted, then no author shall be associated with the parent annotation
+     * type.
+     *
+     * @param author author of the current comment
+     */
+    public void setAuthor(String author) {
+        ctComment.setAuthor(author);
+    }
+
+    /**
+     * Get the initials of the author of the current comment
+     *
+     * @return initials the initials of the author of the current comment
+     */
+    public String getInitials() {
+        return ctComment.getInitials();
+    }
+
+    /**
+     * Specifies the initials of the author of the current comment
+     *
+     * @param initials the initials of the author of the current comment
+     */
+    public void setInitials(String initials) {
+        ctComment.setInitials(initials);
+    }
+
+    /**
+     * Get the date information of the current comment
+     *
+     * @return the date information for the current comment.
+     */
+    public Calendar getDate() {
+        return ctComment.getDate();
+    }
+
+    /**
+     * Specifies the date information for the current comment. If this attribute
+     * is omitted, then no date information shall be associated with the parent
+     * annotation type.
+     *
+     * @param date the date information for the current comment.
+     */
+    public void setDate(Calendar date) {
+        ctComment.setDate(date);
+    }
+
 }
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java
new file mode 100644
index 0000000..3cf82b6
--- /dev/null
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFComments.java
@@ -0,0 +1,280 @@
+/* ====================================================================
+   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.poi.xwpf.usermodel;
+
+import org.apache.poi.ooxml.POIXMLDocumentPart;
+import org.apache.poi.ooxml.POIXMLException;
+import org.apache.poi.ooxml.POIXMLRelation;
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
+import org.apache.poi.openxml4j.opc.PackagePart;
+import org.apache.poi.util.IOUtils;
+import org.apache.poi.util.Internal;
+import org.apache.xmlbeans.XmlException;
+import org.apache.xmlbeans.XmlOptions;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComment;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CTComments;
+import org.openxmlformats.schemas.wordprocessingml.x2006.main.CommentsDocument;
+
+import javax.xml.namespace.QName;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.apache.poi.ooxml.POIXMLTypeLoader.DEFAULT_XML_OPTIONS;
+
+/**
+ * specifies all of the comments defined in the current document
+ */
+public class XWPFComments extends POIXMLDocumentPart {
+
+    XWPFDocument document;
+    private List<XWPFComment> comments = new ArrayList<>();
+    private List<XWPFPictureData> pictures = new ArrayList<>();
+    private CTComments ctComments;
+
+    /**
+     * Construct XWPFComments from a package part
+     *
+     * @param part the package part holding the data of the footnotes,
+     */
+    public XWPFComments(PackagePart part) {
+        super(part);
+    }
+
+    /**
+     * Construct XWPFComments from scratch for a new document.
+     */
+    public XWPFComments() {
+        ctComments = CTComments.Factory.newInstance();
+    }
+
+    /**
+     * read comments form an existing package
+     */
+    @Override
+    public void onDocumentRead() throws IOException {
+        try (InputStream is = getPackagePart().getInputStream()) {
+            CommentsDocument doc = CommentsDocument.Factory.parse(is, DEFAULT_XML_OPTIONS);
+            ctComments = doc.getComments();
+            for (CTComment ctComment : ctComments.getCommentList()) {
+                comments.add(new XWPFComment(ctComment, this));
+            }
+        } catch (XmlException e) {
+            throw new POIXMLException("Unable to read comments", e);
+        }
+
+        for (POIXMLDocumentPart poixmlDocumentPart : getRelations()) {
+            if (poixmlDocumentPart instanceof XWPFPictureData) {
+                XWPFPictureData xwpfPicData = (XWPFPictureData) poixmlDocumentPart;
+                pictures.add(xwpfPicData);
+                document.registerPackagePictureData(xwpfPicData);
+            }
+        }
+    }
+
+    /**
+     * Adds a picture to the comments.
+     *
+     * @param is     The stream to read image from
+     * @param format The format of the picture.
+     * @return the index to this picture (0 based), the added picture can be
+     * obtained from {@link #getAllPictures()} .
+     * @throws InvalidFormatException If the format of the picture is not known.
+     * @throws IOException            If reading the picture-data from the stream fails.
+     */
+    public String addPictureData(InputStream is, int format) throws InvalidFormatException, IOException {
+        byte[] data = IOUtils.toByteArray(is);
+        return addPictureData(data, format);
+    }
+
+    /**
+     * Adds a picture to the comments.
+     *
+     * @param pictureData The picture data
+     * @param format      The format of the picture.
+     * @return the index to this picture (0 based), the added picture can be
+     * obtained from {@link #getAllPictures()} .
+     * @throws InvalidFormatException If the format of the picture is not known.
+     */
+    public String addPictureData(byte[] pictureData, int format) throws InvalidFormatException {
+        XWPFPictureData xwpfPicData = document.findPackagePictureData(pictureData, format);
+        POIXMLRelation relDesc = XWPFPictureData.RELATIONS[format];
+
+        if (xwpfPicData == null) {
+            /* Part doesn't exist, create a new one */
+            int idx = getXWPFDocument().getNextPicNameNumber(format);
+            xwpfPicData = (XWPFPictureData) createRelationship(relDesc, XWPFFactory.getInstance(), idx);
+            /* write bytes to new part */
+            PackagePart picDataPart = xwpfPicData.getPackagePart();
+            try (OutputStream out = picDataPart.getOutputStream()) {
+                out.write(pictureData);
+            } catch (IOException e) {
+                throw new POIXMLException(e);
+            }
+
+            document.registerPackagePictureData(xwpfPicData);
+            pictures.add(xwpfPicData);
+            return getRelationId(xwpfPicData);
+        } else if (!getRelations().contains(xwpfPicData)) {
+            /*
+             * Part already existed, but was not related so far. Create
+             * relationship to the already existing part and update
+             * POIXMLDocumentPart data.
+             */
+            // TODO add support for TargetMode.EXTERNAL relations.
+            RelationPart rp = addRelation(null, XWPFRelation.IMAGES, xwpfPicData);
+            pictures.add(xwpfPicData);
+            return rp.getRelationship().getId();
+        } else {
+            /* Part already existed, get relation id and return it */
+            return getRelationId(xwpfPicData);
+        }
+    }
+
+    /**
+     * save and commit comments
+     */
+    @Override
+    protected void commit() throws IOException {
+        XmlOptions xmlOptions = new XmlOptions(DEFAULT_XML_OPTIONS);
+        xmlOptions.setSaveSyntheticDocumentElement(new QName(
+                CTComments.type.getName().getNamespaceURI(), "comments"));
+        PackagePart part = getPackagePart();
+        OutputStream out = part.getOutputStream();
+        ctComments.save(out, xmlOptions);
+        out.close();
+    }
+
+    public List<XWPFPictureData> getAllPictures() {
+        return Collections.unmodifiableList(pictures);
+    }
+
+    /**
+     * Gets the underlying CTComments object for the comments.
+     *
+     * @return CTComments object
+     */
+    public CTComments getCtComments() {
+        return ctComments;
+    }
+
+    /**
+     * set a new comments
+     */
+    @Internal
+    public void setCtComments(CTComments ctComments) {
+        this.ctComments = ctComments;
+    }
+
+    /**
+     * Get the list of {@link XWPFComment} in the Comments part.
+     *
+     * @return
+     */
+    public List<XWPFComment> getComments() {
+        return comments;
+    }
+
+    /**
+     * Get the specified comment by position
+     *
+     * @param pos Array position of the comment
+     * @return
+     */
+    public XWPFComment getComment(int pos) {
+        if (pos >= 0 && pos < ctComments.sizeOfCommentArray()) {
+            return getComments().get(pos);
+        }
+        return null;
+    }
+
+    /**
+     * Get the specified comment by comment id
+     *
+     * @param id comment id
+     * @return the specified comment
+     */
+    public XWPFComment getCommentByID(String id) {
+        for (XWPFComment comment : comments) {
+            if (comment.getId().equals(id)) {
+                return comment;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Get the specified comment by ctComment
+     *
+     * @param ctComment
+     * @return
+     */
+    public XWPFComment getComment(CTComment ctComment) {
+        for (int i = 0; i < comments.size(); i++) {
+            if (comments.get(i).getCtComment() == ctComment) {
+                return comments.get(i);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Create a new comment and add it to the document.
+     *
+     * @param cid comment Id
+     * @return
+     */
+    public XWPFComment createComment(BigInteger cid) {
+        CTComment ctComment = ctComments.addNewComment();
+        ctComment.setId(cid);
+        XWPFComment comment = new XWPFComment(ctComment, this);
+        comments.add(comment);
+        return comment;
+    }
+
+    /**
+     * Remove the specified comment if present.
+     *
+     * @param pos Array position of the comment to be removed
+     * @return True if the comment was removed.
+     */
+    public boolean removeComment(int pos) {
+        if (pos >= 0 && pos < ctComments.sizeOfCommentArray()) {
+            comments.remove(pos);
+            ctComments.removeComment(pos);
+            return true;
+        }
+        return false;
+    }
+
+    public XWPFDocument getXWPFDocument() {
+        if (null != document) {
+            return document;
+        }
+        return (XWPFDocument) getParent();
+    }
+
+    public void setXWPFDocument(XWPFDocument document) {
+        this.document = document;
+    }
+
+}
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
index e454365..fcf58ed 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFDocument.java
@@ -85,7 +85,6 @@
 
     protected List<XWPFFooter> footers = new ArrayList<>();
     protected List<XWPFHeader> headers = new ArrayList<>();
-    protected List<XWPFComment> comments = new ArrayList<>();
     protected List<XWPFHyperlink> hyperlinks = new ArrayList<>();
     protected List<XWPFParagraph> paragraphs = new ArrayList<>();
     protected List<XWPFTable> tables = new ArrayList<>();
@@ -99,6 +98,7 @@
     protected XWPFFootnotes footnotes;
     private CTDocument1 ctDocument;
     private XWPFSettings settings;
+    private XWPFComments comments;
     protected final List<XWPFChart> charts = new ArrayList<>();
     /**
      * Keeps track on all id-values used in this document and included parts, like headers, footers, etc.
@@ -217,11 +217,8 @@
                     headers.add(header);
                     header.onDocumentRead();
                 } else if (relation.equals(XWPFRelation.COMMENT.getRelation())) {
-                    // TODO Create according XWPFComment class, extending POIXMLDocumentPart
-                    CommentsDocument cmntdoc = CommentsDocument.Factory.parse(p.getPackagePart().getInputStream(), DEFAULT_XML_OPTIONS);
-                    for (CTComment ctcomment : cmntdoc.getComments().getCommentArray()) {
-                        comments.add(new XWPFComment(ctcomment, this));
-                    }
+                    this.comments = (XWPFComments) p;
+                    this.comments.onDocumentRead();
                 } else if (relation.equals(XWPFRelation.SETTINGS.getRelation())) {
                     settings = (XWPFSettings) p;
                     settings.onDocumentRead();
@@ -431,18 +428,27 @@
         return hyperlinks.toArray(new XWPFHyperlink[0]);
     }
 
-    public XWPFComment getCommentByID(String id) {
-        for (XWPFComment comment : comments) {
-            if (comment.getId().equals(id)) {
-                return comment;
-            }
-        }
+    /**
+     * Get Comments
+     *
+     * @return comments
+     */
+    public XWPFComments getDocComments() {
+        return comments;
+    }
 
-        return null;
+    public XWPFComment getCommentByID(String id) {
+        if (null == comments) {
+            return null;
+        }
+        return comments.getCommentByID(id);
     }
 
     public XWPFComment[] getComments() {
-        return comments.toArray(new XWPFComment[0]);
+        if (null == comments) {
+            return null;
+        }
+        return comments.getComments().toArray(new XWPFComment[0]);
     }
 
     /**
@@ -837,6 +843,27 @@
         paragraphs.add(p);
         return p;
     }
+    
+    
+    /**
+     * Creates an empty comments for the document if one does not already exist
+     * 
+     * @return comments
+     */
+    public XWPFComments createComments() {
+        if (comments == null) {
+            CommentsDocument commentsDoc = CommentsDocument.Factory.newInstance();
+
+            XWPFRelation relation = XWPFRelation.COMMENT;
+            int i = getRelationIndex(relation);
+
+            XWPFComments wrapper = (XWPFComments) createRelationship(relation, XWPFFactory.getInstance(), i);
+            wrapper.setCtComments(commentsDoc.addNewComments());
+            wrapper.setXWPFDocument(getXWPFDocument());
+            comments = wrapper;
+        }
+        return comments;
+    }
 
     /**
      * Creates an empty numbering if one does not already exist and sets the numbering member
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
index 7ba9e06..d67a830 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRelation.java
@@ -137,9 +137,10 @@
         null
     );
     public static final XWPFRelation COMMENT = new XWPFRelation(
-        null,
+        "application/vnd.openxmlformats-officedocument.wordprocessingml.comments+xml",
         "http://schemas.openxmlformats.org/officeDocument/2006/relationships/comments",
-        null
+        "/word/comments.xml",
+        XWPFComments::new, XWPFComments::new
     );
     public static final XWPFRelation FOOTNOTE = new XWPFRelation(
         "application/vnd.openxmlformats-officedocument.wordprocessingml.footnotes+xml",
diff --git a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
index 06df99b..ee741ba 100644
--- a/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
+++ b/src/ooxml/java/org/apache/poi/xwpf/usermodel/XWPFRun.java
@@ -1082,6 +1082,10 @@
             XWPFHeaderFooter headerFooter = (XWPFHeaderFooter) parent.getPart();
             relationId = headerFooter.addPictureData(pictureData, pictureType);
             picData = (XWPFPictureData) headerFooter.getRelationById(relationId);
+        } else if (parent.getPart() instanceof XWPFComments) {
+            XWPFComments comments = (XWPFComments) parent.getPart();
+            relationId = comments.addPictureData(pictureData, pictureType);
+            picData = (XWPFPictureData) comments.getRelationById(relationId);
         } else {
             @SuppressWarnings("resource")
             XWPFDocument doc = parent.getDocument();
diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
index 5f0d899..f1ba777 100644
--- a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
+++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComment.java
@@ -16,14 +16,19 @@
 ==================================================================== */
 package org.apache.poi.xwpf.usermodel;
 
+import org.apache.poi.openxml4j.exceptions.InvalidFormatException;
 import org.apache.poi.xwpf.XWPFTestDataSamples;
 import org.junit.jupiter.api.Test;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
+import java.math.BigInteger;
+import java.util.Calendar;
 
-import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.*;
 
 class TestXWPFComment {
+
     @Test
     void testText() throws IOException {
         try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("comment.docx")) {
@@ -34,4 +39,83 @@
             assertEquals("This is the first line\n\nThis is the second line", comment.getText());
         }
     }
+
+    @Test
+    public void testAddComment() throws IOException {
+        BigInteger cId = BigInteger.valueOf(0);
+        Calendar date = Calendar.getInstance();
+        try (XWPFDocument docOut = new XWPFDocument()) {
+            assertNull(docOut.getDocComments());
+
+            XWPFComments comments = docOut.createComments();
+            XWPFComment comment = comments.createComment(cId);
+            comment.setAuthor("Author");
+            comment.setInitials("s");
+            comment.setDate(date);
+
+            XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+            assertEquals(1, docIn.getComments().length);
+            comment = docIn.getCommentByID(cId.toString());
+            assertNotNull(comment);
+            assertEquals("Author", comment.getAuthor());
+            assertEquals("s", comment.getInitials());
+            assertEquals(date.getTimeInMillis(), comment.getDate().getTimeInMillis());
+        }
+    }
+
+    @Test
+    void testRemoveComment() throws IOException {
+        try (XWPFDocument doc = XWPFTestDataSamples.openSampleDocument("comment.docx")) {
+            assertEquals(1, doc.getComments().length);
+
+            doc.getDocComments().removeComment(0);
+
+            XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+            assertEquals(0, docIn.getComments().length);
+        }
+    }
+
+    @Test
+    void testCreateParagraph() throws IOException {
+        try (XWPFDocument doc = new XWPFDocument()) {
+            XWPFComments comments = doc.createComments();
+            XWPFComment comment = comments.createComment(BigInteger.ONE);
+            XWPFParagraph paragraph = comment.createParagraph();
+            paragraph.createRun().setText("comment paragraph text");
+
+            XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+            XWPFComment xwpfComment = docIn.getCommentByID("1");
+            assertEquals(1, xwpfComment.getParagraphs().size());
+            String text = xwpfComment.getParagraphArray(0).getText();
+            assertEquals("comment paragraph text", text);
+        }
+    }
+
+    @Test
+    void testAddPicture() throws IOException, InvalidFormatException {
+        try (XWPFDocument doc = new XWPFDocument()) {
+            XWPFComments comments = doc.createComments();
+            XWPFComment comment = comments.createComment(BigInteger.ONE);
+            XWPFParagraph paragraph = comment.createParagraph();
+            XWPFRun r = paragraph.createRun();
+            r.addPicture(new ByteArrayInputStream(new byte[0]),
+                    Document.PICTURE_TYPE_JPEG, "test.jpg", 21, 32);
+
+            assertEquals(1, comments.getAllPictures().size());
+            assertEquals(1, doc.getAllPackagePictures().size());
+        }
+    }
+
+    @Test
+    void testCreateTable() throws IOException {
+        try (XWPFDocument doc = new XWPFDocument()) {
+            XWPFComments comments = doc.createComments();
+            XWPFComment comment = comments.createComment(BigInteger.ONE);
+            comment.createTable(1, 1);
+
+            XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(doc);
+            XWPFComment xwpfComment = docIn.getCommentByID("1");
+            assertEquals(1, xwpfComment.getTables().size());
+        }
+    }
 }
diff --git a/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java
new file mode 100644
index 0000000..dad5ab8
--- /dev/null
+++ b/src/ooxml/testcases/org/apache/poi/xwpf/usermodel/TestXWPFComments.java
@@ -0,0 +1,62 @@
+/* ====================================================================
+   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.poi.xwpf.usermodel;
+
+import org.apache.poi.xwpf.XWPFTestDataSamples;
+import org.junit.jupiter.api.Test;
+
+import java.io.IOException;
+import java.math.BigInteger;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+class TestXWPFComments {
+
+    @Test
+    void testAddCommentsToDoc() throws IOException {
+        BigInteger cId = BigInteger.ZERO;
+        try (XWPFDocument docOut = new XWPFDocument()) {
+            assertNull(docOut.getDocComments());
+
+            // create comments
+            XWPFComments comments = docOut.createComments();
+            assertNotNull(comments);
+            assertSame(comments, docOut.createComments());
+
+            // create comment
+            XWPFComment comment = comments.createComment(cId);
+            comment.setAuthor("Author");
+            comment.createParagraph().createRun().setText("comment paragraph");
+
+            // apply comment to run text
+            XWPFParagraph paragraph = docOut.createParagraph();
+            paragraph.getCTP().addNewCommentRangeStart().setId(cId);
+            paragraph.getCTP().addNewR().addNewT().setStringValue("HelloWorld");
+            paragraph.getCTP().addNewCommentRangeEnd().setId(cId);
+            paragraph.getCTP().addNewR().addNewCommentReference().setId(cId);
+
+            // check
+            XWPFDocument docIn = XWPFTestDataSamples.writeOutAndReadBack(docOut);
+            assertNotNull(docIn.getDocComments());
+            assertEquals(1, docIn.getComments().length);
+            comment = docIn.getCommentByID("0");
+            assertTrue(null != comment);
+            assertEquals("Author", comment.getAuthor());
+        }
+    }
+
+}