Skip to content

Commit

Permalink
Refactor and document various classes in docxstamper package (#234)
Browse files Browse the repository at this point in the history
Refactor wires simplified and cleaned up for better readability in
various classes such as CommentUtil, Matcher, and ExpressionResolver.
Additional JavaDoc documentation has been added to further enhance
understanding. Changes in ProcessorExceptionHandler improve thread
safety with CopyOnWriteArrayList and offer extended documentation. Minor
changes have been applied in ThrowingRunnable class too.
  • Loading branch information
caring-coder authored Feb 10, 2024
2 parents a593686 + 26149fb commit a9919f1
Show file tree
Hide file tree
Showing 5 changed files with 73 additions and 78 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@
* @version $Id: $Id
*/
public class ExpressionResolver {
public static final Matcher DEFAULT_MATCHER = new Matcher("${", "}");
public static final Matcher SECONDARY_MATCHER = new Matcher("#{", "}");
private static final Matcher DEFAULT_MATCHER = new Matcher("${", "}");
private static final Matcher SECONDARY_MATCHER = new Matcher("#{", "}");
private final ExpressionParser parser;
private final StandardEvaluationContext evaluationContext;

Expand All @@ -32,6 +32,12 @@ public ExpressionResolver(
this.evaluationContext = standardEvaluationContext;
}

/**
* Cleans the given expression by stripping the prefix and suffix if they match any of the configured matchers.
*
* @param expression the expression to clean.
* @return the cleaned expression.
*/
public static String cleanExpression(String expression) {
if (DEFAULT_MATCHER.match(expression))
return DEFAULT_MATCHER.strip(expression);
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/org/wickedsource/docxstamper/el/Matcher.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package org.wickedsource.docxstamper.el;

/**
* Represents a Matcher that checks if an expression starts with a specified prefix and ends with a specified suffix.
*/
public record Matcher(String prefix, String suffix) {
boolean match(String expression) {
assertNotNull(expression);
Expand Down
105 changes: 44 additions & 61 deletions src/main/java/org/wickedsource/docxstamper/util/CommentUtil.java
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,11 @@
* Utility class for working with comments in a DOCX document.
*/
public class CommentUtil {
private static final Logger logger = LoggerFactory.getLogger(
CommentUtil.class);
private static final Logger logger = LoggerFactory.getLogger(CommentUtil.class);
private static final String WORD_COMMENTS_PART_NAME = "/word/comments.xml";

private CommentUtil() {
throw new DocxStamperException(
"Utility class shouldn't be instantiated");
throw new DocxStamperException("Utility class shouldn't be instantiated");
}

/**
Expand All @@ -42,28 +40,24 @@ private CommentUtil() {
* @return Optional of the comment, if found, Optional.empty() otherwise.
*/
public static Optional<Comments.Comment> getCommentAround(
R run,
WordprocessingMLPackage document
R run, WordprocessingMLPackage document
) {
if (run == null)
return Optional.empty();
if (run == null) return Optional.empty();

ContentAccessor parent = (ContentAccessor) ((Child) run).getParent();
if (parent == null)
return Optional.empty();
if (parent == null) return Optional.empty();

try {
return getComment(run, document, parent);
} catch (Docx4JException e) {
throw new DocxStamperException(
"error accessing the comments of the document!", e);
"error accessing the comments of the document!",
e);
}
}

private static Optional<Comments.Comment> getComment(
R run,
WordprocessingMLPackage document,
ContentAccessor parent
R run, WordprocessingMLPackage document, ContentAccessor parent
) throws Docx4JException {
CommentRangeStart possibleComment = null;
boolean foundChild = false;
Expand All @@ -75,9 +69,8 @@ private static Optional<Comments.Comment> getComment(
else if (possibleComment != null && run.equals(contentElement))
foundChild = true;
// and then if we have an end of a comment we are good!
else if (possibleComment != null
&& foundChild
&& unwrap(contentElement) instanceof CommentRangeEnd) {
else if (possibleComment != null && foundChild && unwrap(
contentElement) instanceof CommentRangeEnd) {
try {
var id = possibleComment.getId();
return findComment(document, id);
Expand All @@ -96,9 +89,16 @@ && unwrap(contentElement) instanceof CommentRangeEnd) {
return Optional.empty();
}

/**
* Finds a comment with the given ID in the specified WordprocessingMLPackage document.
*
* @param document the WordprocessingMLPackage document to search for the comment
* @param id the ID of the comment to find
* @return an Optional containing the Comment if found, or an empty Optional if not found
* @throws Docx4JException if an error occurs while searching for the comment
*/
public static Optional<Comments.Comment> findComment(
WordprocessingMLPackage document,
BigInteger id
WordprocessingMLPackage document, BigInteger id
) throws Docx4JException {
var name = new PartName(WORD_COMMENTS_PART_NAME);
var parts = document.getParts();
Expand All @@ -119,8 +119,7 @@ public static Optional<Comments.Comment> findComment(
* @return the comment, if found, null otherwise.
*/
public static String getCommentStringFor(
ContentAccessor object,
WordprocessingMLPackage document
ContentAccessor object, WordprocessingMLPackage document
) {
Comments.Comment comment = getCommentFor(object,
document).orElseThrow();
Expand All @@ -139,12 +138,10 @@ public static String getCommentStringFor(
* null if the specified object is not commented.
*/
public static Optional<Comments.Comment> getCommentFor(
ContentAccessor object,
WordprocessingMLPackage document
ContentAccessor object, WordprocessingMLPackage document
) {
for (Object contentObject : object.getContent()) {
if (!(contentObject instanceof CommentRangeStart crs))
continue;
if (!(contentObject instanceof CommentRangeStart crs)) continue;
BigInteger id = crs.getId();
PartName partName;
try {
Expand All @@ -162,7 +159,8 @@ public static Optional<Comments.Comment> getCommentFor(
comments = commentsPart.getContents();
} catch (Docx4JException e) {
throw new DocxStamperException(
"error accessing the comments of the document!", e);
"error accessing the comments of the document!",
e);
}

for (Comments.Comment comment : comments.getComment()) {
Expand Down Expand Up @@ -224,8 +222,7 @@ public static void deleteComment(CommentWrapper comment) {
* @param commentId a {@link java.math.BigInteger} object
*/
public static void deleteCommentFromElement(
List<Object> items,
BigInteger commentId
List<Object> items, BigInteger commentId
) {
List<Object> elementsToRemove = new ArrayList<>();
for (Object item : items) {
Expand Down Expand Up @@ -254,6 +251,7 @@ public static void deleteCommentFromElement(

/**
* Extracts all comments from the given document.
*
* @param document the document to extract comments from.
* @return a map of all comments, with the key being the comment id.
*/
Expand All @@ -274,12 +272,10 @@ private static Map<BigInteger, CommentWrapper> cleanMalformedComments(Map<BigInt
if (isCommentMalformed(comment)) {
logger.error(
"Skipping malformed comment, missing range start and/or range end : {}",
getCommentContent(comment)
);
getCommentContent(comment));
} else {
filteredCommentEntries.put(key, comment);
comment.setChildren(
cleanMalformedComments(comment.getChildren()));
comment.setChildren(cleanMalformedComments(comment.getChildren()));
}
});
return filteredCommentEntries;
Expand All @@ -291,25 +287,21 @@ private static Set<CommentWrapper> cleanMalformedComments(Set<CommentWrapper> ch
if (isCommentMalformed(comment)) {
logger.error(
"Skipping malformed comment, missing range start and/or range end : {}",
getCommentContent(comment)
);
getCommentContent(comment));
return false;
}
comment.setChildren(
cleanMalformedComments(comment.getChildren()));
comment.setChildren(cleanMalformedComments(comment.getChildren()));
return true;
})
.collect(toSet());
}

private static String getCommentContent(CommentWrapper comment) {
return comment.getComment() != null
? comment.getComment()
return comment.getComment() != null ? comment.getComment()
.getContent()
.stream()
.map(TextUtils::getText)
.collect(Collectors.joining(""))
: "<no content>";
.collect(Collectors.joining("")) : "<no content>";
}

private static boolean isCommentMalformed(CommentWrapper comment) {
Expand All @@ -321,10 +313,8 @@ private static void collectCommentRanges(
Map<BigInteger, CommentWrapper> allComments,
WordprocessingMLPackage document
) {
Queue<CommentWrapper> stack = Collections.asLifoQueue(
new ArrayDeque<>());
DocumentWalker documentWalker = new BaseDocumentWalker(
document.getMainDocumentPart()) {
Queue<CommentWrapper> stack = Collections.asLifoQueue(new ArrayDeque<>());
DocumentWalker documentWalker = new BaseDocumentWalker(document.getMainDocumentPart()) {
@Override
protected void onCommentRangeStart(CommentRangeStart commentRangeStart) {
CommentWrapper commentWrapper = allComments.get(
Expand All @@ -347,32 +337,25 @@ protected void onCommentRangeStart(CommentRangeStart commentRangeStart) {

@Override
protected void onCommentRangeEnd(CommentRangeEnd commentRangeEnd) {
CommentWrapper commentWrapper = allComments.get(
commentRangeEnd.getId());
if (commentWrapper == null)
throw new DocxStamperException(
"Found a comment range end before the comment range start !");
CommentWrapper commentWrapper = allComments.get(commentRangeEnd.getId());
if (commentWrapper == null) throw new DocxStamperException(
"Found a comment range end before the comment range start !");

commentWrapper.setCommentRangeEnd(commentRangeEnd);

if (stack.isEmpty())
return;
if (stack.isEmpty()) return;

if (stack.peek()
.equals(commentWrapper))
stack.remove();
else
throw new DocxStamperException(
"Cannot figure which comment contains the other !");
.equals(commentWrapper)) stack.remove();
else throw new DocxStamperException(
"Cannot figure which comment contains the other !");
}

@Override
protected void onCommentReference(R.CommentReference commentReference) {
CommentWrapper commentWrapper = allComments.get(
commentReference.getId());
if (commentWrapper == null)
throw new DocxStamperException(
"Found a comment reference before the comment range start !");
CommentWrapper commentWrapper = allComments.get(commentReference.getId());
if (commentWrapper == null) throw new DocxStamperException(
"Found a comment reference before the comment range start !");
commentWrapper.setCommentReference(commentReference);
}
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,33 +1,36 @@
package pro.verron.docxstamper.utils;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.atomic.AtomicReference;

/**
* This class acts as an exception handler for multithreading execution,
* specifically for uncaught exceptions.
* It stores the exception and performs predefined routines
* when an exception occurs.
* The ProcessorExceptionHandler class is responsible for capturing and handling uncaught exceptions that occur in a thread.
* It implements the Thread.UncaughtExceptionHandler interface and can be assigned to a thread using the setUncaughtExceptionHandler() method.
* When an exception occurs in the thread, the uncaughtException() method of ProcessorExceptionHandler will be called.
* This class provides the following features:
* 1. Capturing and storing the uncaught exception.
* 2. Executing a list of routines when an exception occurs.
* 3. Providing access to the captured exception, if any.
* Example usage:
* ProcessorExceptionHandler exceptionHandler = new ProcessorExceptionHandler();
* thread.setUncaughtExceptionHandler(exceptionHandler);
*
* <ul><p>Methods :</p>
* <li>uncaughtException() : Captures and stores an uncaught exception
* from a thread run and executes all defined routines on occurrence
* of the exception.</li>
* <li>onException() : Adds a routine to the list of routines that should be run
* when an exception occurs.</li>
* <li>exception() : Returns the captured exception if present.</li>
* </ul>
* @see Thread.UncaughtExceptionHandler
*/
public class ProcessorExceptionHandler
implements Thread.UncaughtExceptionHandler {
private final AtomicReference<Throwable> exception;
private final List<Runnable> onException;

/**
* Constructs a new ProcessorExceptionHandler for managing thread's uncaught exceptions.
* Once set to a thread, it retains the exception information and performs specified routines.
*/
public ProcessorExceptionHandler() {
this.exception = new AtomicReference<>();
onException = new ArrayList<>();
this.onException = new CopyOnWriteArrayList<>();
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ default void run() {
/**
* Executes the runnable task
*
* @throws DocxStamperException if an exception occurs executing the task
* @throws Exception if an exception occurs executing the task
*/
void throwingRun() throws Exception;
}

0 comments on commit a9919f1

Please sign in to comment.