Skip to content

Commit

Permalink
Clean up conditional resolving
Browse files Browse the repository at this point in the history
  • Loading branch information
AntonOellerer committed Mar 15, 2022
1 parent d788e81 commit 5ae6dde
Show file tree
Hide file tree
Showing 9 changed files with 103 additions and 51 deletions.
6 changes: 3 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ plugins {
}

group 'com.docutools'
version = '1.4.0-alpha.20'
version = '1.4.0-alpha.21'

sourceCompatibility = "17.PREVIEW"
targetCompatibility = "17.PREVIEW"
sourceCompatibility = "17"
targetCompatibility = "17"

repositories {
mavenCentral()
Expand Down
4 changes: 2 additions & 2 deletions src/main/java/com/docutools/jocument/PlaceholderData.java
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ default void transform(Object placeholder, GenerationOptions options) {
}

/**
* Evaluates if this {@link PlaceholderData} is a truphy value. Non-truphy values are defined as:
* Evaluates if this {@link PlaceholderData} is a truthy value. Non-truthy values are defined as:
*
* <ul>
* <li>Empty string</li>
Expand All @@ -66,7 +66,7 @@ default void transform(Object placeholder, GenerationOptions options) {
* <li>{@code false}</li>
* </ul>
*
* @return {@code true} if truphy
* @return {@code true} if truthy
*/
default boolean isTruthy() {
return count() > 0;
Expand Down
37 changes: 22 additions & 15 deletions src/main/java/com/docutools/jocument/impl/ReflectionResolver.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ public class ReflectionResolver extends PlaceholderResolver {
private static final Logger logger = LogManager.getLogger();

protected final Object bean;
private final PropertyUtilsBean pub = new PropertyUtilsBean();
protected final CustomPlaceholderRegistry customPlaceholderRegistry;
private final PropertyUtilsBean pub = new PropertyUtilsBean();
private final PlaceholderMapper placeholderMapper = new PlaceholderMapperImpl();
private final PlaceholderResolver parent;

Expand Down Expand Up @@ -143,21 +143,22 @@ private static DateTimeFormatter toDateTimeFormatter(Format format) {
public Optional<PlaceholderData> resolve(String placeholderName, Locale locale) {
logger.debug("Trying to resolve placeholder {}", placeholderName);
boolean isCondition = placeholderName.endsWith("?");
var strippedPlaceholderName = isCondition? placeholderName.substring(0, placeholderName.length()-1) : placeholderName;
var result = tryMatchPattern(strippedPlaceholderName, locale)
.or(() -> resolveAccessor(strippedPlaceholderName, locale)
.or(() -> placeholderMapper.map(strippedPlaceholderName)
.flatMap(mappedName -> resolve(mappedName, locale))));
if(isCondition) {
if(result.isEmpty())
return Optional.of(new IterablePlaceholderData()); // TODO sure?
var resultValue = result.get();
return Optional.of(resultValue.isTruthy()? new IterablePlaceholderData(this) : new IterablePlaceholderData());
placeholderName = isCondition ? placeholderName.substring(0, placeholderName.length() - 1) : placeholderName;
Optional<PlaceholderData> result = resolveStripped(locale, placeholderName);
if (isCondition) {
return evaluateCondition(result);
}
return result;
}

private Optional<PlaceholderData> tryMatchPattern(String placeholderName, Locale locale) {
private Optional<PlaceholderData> resolveStripped(Locale locale, String placeholder) {
return matchPattern(placeholder, locale)
.or(() -> resolveAccessor(placeholder, locale)
.or(() -> placeholderMapper.map(placeholder)
.flatMap(mappedName -> resolve(mappedName, locale))));
}

private Optional<PlaceholderData> matchPattern(String placeholderName, Locale locale) {
var beanClass = bean.getClass();
return Arrays.stream(beanClass.getMethods())
.filter(method -> Optional.ofNullable(method.getAnnotation(MatchPlaceholder.class))
Expand Down Expand Up @@ -236,9 +237,8 @@ private Optional<PlaceholderData> tryResolveInParent(String placeholderName, Loc
}

/**
* Method resolving placeholders for the reflection resolver.
* incredibly ugly, but since doResolve is public, the overriden method of FutureReflectionResolver is used when necessary
* could/should maybe be made a bit more explicit by defining a common superinterface
* Method resolving placeholders for the reflection resolver. incredibly ugly, but since doResolve is public, the overriden method of
* FutureReflectionResolver is used when necessary could/should maybe be made a bit more explicit by defining a common superinterface
*
* @param placeholderName The name of the placeholder
* @param locale The locale to user for localization
Expand Down Expand Up @@ -327,6 +327,13 @@ protected Object getBeanProperty(String placeholderName) throws InvocationTarget
}
}

private Optional<PlaceholderData> evaluateCondition(Optional<PlaceholderData> result) {
if (result.isPresent() && result.get().isTruthy()) {
return Optional.of(new IterablePlaceholderData(this));
}
return Optional.of(new IterablePlaceholderData());
}

protected Optional<PlaceholderData> formatTemporal(String placeholderName, Temporal time, Locale locale) {
Optional<DateTimeFormatter> formatter;
if (isFieldAnnotatedWith(bean.getClass(), placeholderName, Format.class)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,19 +33,15 @@ public String toString() {
@Override
public boolean isTruthy() {
// TODO can be replaced with Java pattern matching when out of preview
if(value instanceof Number number) {
if (value instanceof Number number) {
return number.longValue() != 0L;
}
if(value instanceof String string) {
} else if (value instanceof String string) {
return !string.isEmpty();
}
if(value instanceof Boolean bool) {
} else if (value instanceof Boolean bool) {
return bool;
}
if(value instanceof Collection<?> collection) {
} else if (value instanceof Collection<?> collection) {
return !collection.isEmpty();
}
if(value instanceof Object[] array) {
} else if (value instanceof Object[] array) {
return array.length > 0;
}
return value != null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.ss.usermodel.Cell;
Expand All @@ -23,9 +20,8 @@


/**
* This class is responsible for creating excel report rows from template rows.
* It is used recursively when resolving nested loops.
* Because of this, the generator is agnostic of enclosing structures like sheets and workbooks.
* This class is responsible for creating excel report rows from template rows. It is used recursively when resolving nested loops. Because of this,
* the generator is agnostic of enclosing structures like sheets and workbooks.
*
* @author Anton Oellerer
* @since 2020-04-10
Expand Down Expand Up @@ -75,7 +71,7 @@ private void generate() {
excelWriter.addCell(cell);
} else {
var newCellText = resolver.resolve(ExcelUtils.getPlaceholder(cell))
.orElse(new ScalarPlaceholderData<>("-"));
.orElse(new ScalarPlaceholderData<>(""));
excelWriter.addCell(cell, newCellText.toString());
}
}
Expand Down Expand Up @@ -159,9 +155,9 @@ private boolean isLoopStart(Row row) {
var cell = row.getCell(row.getFirstCellNum());
if (cell.getCellType() == CellType.STRING) {
return resolver.resolve(
ParsingUtils.stripBrackets(
cell.getStringCellValue()
)).map(PlaceholderData::getType)
ParsingUtils.stripBrackets(
cell.getStringCellValue()
)).map(PlaceholderData::getType)
.map(type -> type == PlaceholderType.SET)
.orElse(false);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
import java.util.List;
import java.util.Locale;
import java.util.regex.MatchResult;
import java.util.stream.Collectors;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.apache.poi.util.LocaleUtil;
Expand Down Expand Up @@ -116,7 +115,7 @@ private List<IBodyElement> getLoopBody(String placeholderName, List<IBodyElement
//Could be written nice with `takeUntil(element -> (element instanceof XP xp && eLM.equals(WU.toString(xp)))
.takeWhile(element -> !(element instanceof XWPFParagraph xwpfParagraph
&& endLoopMarker.equals(WordUtilities.toString(xwpfParagraph))))
.collect(Collectors.toList());
.toList();
}

private boolean isLoopStart(IBodyElement element) {
Expand Down Expand Up @@ -145,6 +144,6 @@ private String fillPlaceholder(MatchResult result, Locale locale) {
logger.debug("Resolving placeholder {}", placeholderName);
return resolver.resolve(placeholderName, locale)
.map(PlaceholderData::toString)
.orElse("-");
.orElse("");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;

import com.docutools.jocument.CustomPlaceholderRegistry;
Expand All @@ -11,12 +12,10 @@
import com.docutools.jocument.TestUtils;
import com.docutools.jocument.impl.CustomPlaceholderRegistryImpl;
import com.docutools.jocument.impl.FutureReflectionResolver;
import com.docutools.jocument.impl.PlaceholderMapperImpl;
import com.docutools.jocument.impl.ReflectionResolver;
import com.docutools.jocument.sample.model.SampleModelData;
import com.docutools.jocument.sample.placeholders.QuotePlaceholder;
import com.docutools.poipath.xwpf.XWPFDocumentWrapper;
import java.awt.Desktop;
import java.io.IOException;
import java.time.LocalDate;
import java.time.Period;
Expand Down Expand Up @@ -243,12 +242,13 @@ void shouldResolveFuture() throws IOException, InterruptedException {
var documentWrapper = new XWPFDocumentWrapper(xwpfDocument);
assertThat(documentWrapper.bodyElement(0).asParagraph().text(), equalTo("Captain: Jean-Luc Picard"));
assertThat(documentWrapper.bodyElement(2).asParagraph().text(), equalTo("First Officer"));
assertThat(documentWrapper.bodyElement(3).asTable().row(0).cell(0).bodyElement(0).asParagraph().text(), equalTo("Name"));
assertThat(documentWrapper.bodyElement(3).asTable().row(0).cell(1).bodyElement(0).asParagraph().text(), equalTo("Rank"));
assertThat(documentWrapper.bodyElement(3).asTable().row(0).cell(2).bodyElement(0).asParagraph().text(), equalTo("Uniform"));
assertThat(documentWrapper.bodyElement(3).asTable().row(1).cell(0).bodyElement(0).asParagraph().text(), equalTo("Riker"));
assertThat(documentWrapper.bodyElement(3).asTable().row(1).cell(1).bodyElement(0).asParagraph().text(), equalTo("3"));
assertThat(documentWrapper.bodyElement(3).asTable().row(1).cell(2).bodyElement(0).asParagraph().text(), equalTo("Red"));
var table = documentWrapper.bodyElement(3).asTable();
assertThat(table.row(0).cell(0).bodyElement(0).asParagraph().text(), equalTo("Name"));
assertThat(table.row(0).cell(1).bodyElement(0).asParagraph().text(), equalTo("Rank"));
assertThat(table.row(0).cell(2).bodyElement(0).asParagraph().text(), equalTo("Uniform"));
assertThat(table.row(1).cell(0).bodyElement(0).asParagraph().text(), equalTo("Riker"));
assertThat(table.row(1).cell(1).bodyElement(0).asParagraph().text(), equalTo("3"));
assertThat(table.row(1).cell(2).bodyElement(0).asParagraph().text(), equalTo("Red"));
assertThat(documentWrapper.bodyElement(6).asParagraph().text(), equalTo("Services"));
assertThat(documentWrapper.bodyElement(8).asParagraph().text(), equalTo("USS Enterprise"));
assertThat(documentWrapper.bodyElement(9).asParagraph().text(), equalTo("US Defiant"));
Expand All @@ -269,7 +269,59 @@ void shouldResolveInScopedMode() throws IOException, InterruptedException {

// Assert
assertThat(document.completed(), is(true));
Desktop.getDesktop().open(document.getPath().toFile());
xwpfDocument = TestUtils.getXWPFDocumentFromDocument(document);
var documentWrapper = new XWPFDocumentWrapper(xwpfDocument);
var table = documentWrapper.bodyElement(0).asTable();
assertThat(table.row(0).cell(0).bodyElement(0).asParagraph().text(), equalTo("Ship"));
assertThat(table.row(0).cell(1).bodyElement(0).asParagraph().text(), equalTo("Crew"));
assertThat(table.row(0).cell(2).bodyElement(0).asParagraph().text(), equalTo("Captain"));
assertThat(table.row(0).cell(3).bodyElement(0).asParagraph().text(), equalTo("Officer"));
assertThat(table.row(1).cell(0).bodyElement(0).asParagraph().text(), equalTo(SampleModelData.ENTERPRISE.name()));
assertThat(table.row(1).cell(1).bodyElement(0).asParagraph().text(), equalTo(String.valueOf(SampleModelData.ENTERPRISE.crew())));
assertThat(table.row(1).cell(2).bodyElement(0).asParagraph().text(), equalTo(SampleModelData.ENTERPRISE.captain().getName()));
assertThat(table.row(1).cell(3).bodyElement(0).asParagraph().text(), equalTo(SampleModelData.ENTERPRISE.captain().getOfficer().getName()));
}

@Test
@DisplayName("Resolve truthy conditional")
void shouldResolveTruthyConditional() throws IOException, InterruptedException {
// Assemble
var template = Template.fromClassPath("/templates/word/ConditionalTemplate.docx")
.orElseThrow();
var resolver = new ReflectionResolver(SampleModelData.ENTERPRISE);

// Act
Document document = template.startGeneration(resolver);
document.blockUntilCompletion(60000L); // 1 minute

// Assert
assertThat(document.completed(), is(true));
xwpfDocument = TestUtils.getXWPFDocumentFromDocument(document);
var documentWrapper = new XWPFDocumentWrapper(xwpfDocument);
var services = SampleModelData.ENTERPRISE.services();
var servicesOnePlanets = services.get(0).getVisitedPlanets();
var servicesTwoPlanets = services.get(1).getVisitedPlanets();
assertThat(documentWrapper.bodyElement(0).asParagraph().text(), equalTo(servicesOnePlanets.get(0).getPlanetName()));
assertThat(documentWrapper.bodyElement(1).asParagraph().text(), equalTo(servicesTwoPlanets.get(0).getPlanetName()));
assertThat(documentWrapper.bodyElement(2).asParagraph().text(), equalTo(servicesTwoPlanets.get(1).getPlanetName()));
}

@Test
@DisplayName("Resolve falsy conditional")
void shouldResolveFalsyConditional() throws IOException, InterruptedException {
// Assemble
var template = Template.fromClassPath("/templates/word/ConditionalTemplate.docx")
.orElseThrow();
var resolver = new ReflectionResolver(SampleModelData.ENTERPRISE_WITHOUT_SERVICES);

// Act
Document document = template.startGeneration(resolver);
document.blockUntilCompletion(60000L); // 1 minute

// Assert
assertThat(document.completed(), is(true));
xwpfDocument = TestUtils.getXWPFDocumentFromDocument(document);
var documentWrapper = new XWPFDocumentWrapper(xwpfDocument);
assertThat(documentWrapper.document().getBodyElements(), hasSize(0));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public class SampleModelData {
public static final Person PICARD_PERSON = new Person("Jean-Luc", "Picard", LocalDate.of(1948, 9, 23));
public static final List<Captain> CAPTAINS;
public static final Ship ENTERPRISE;
public static final Ship ENTERPRISE_WITHOUT_SERVICES;

static {
try {
Expand All @@ -36,6 +37,7 @@ public class SampleModelData {
CompletableFuture.completedFuture(new FirstOfficer("Riker", 3, Uniform.Red)),
CompletableFuture.completedFuture(services),
CompletableFuture.completedFuture(Path.of(SampleModelData.class.getResource("/images/picardProfile.jpg").toURI())));
ENTERPRISE_WITHOUT_SERVICES = new Ship("USS Enterprise", PICARD, 5, Collections.emptyList(), LocalDate.now());
} catch (URISyntaxException e) {
throw new RuntimeException(e);
}
Expand Down
Binary file not shown.

0 comments on commit 5ae6dde

Please sign in to comment.