Repository: incubator-freemarker Updated Branches: refs/heads/3 344b95411 -> 22d3ef2e0
Continued FM2 to FM3 converter... Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/22d3ef2e Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/22d3ef2e Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/22d3ef2e Branch: refs/heads/3 Commit: 22d3ef2e089de184e589a288bbe4fa9031487ec0 Parents: 344b954 Author: ddekany <[email protected]> Authored: Sat Jul 8 22:35:03 2017 +0200 Committer: ddekany <[email protected]> Committed: Sat Jul 8 22:35:03 2017 +0200 ---------------------------------------------------------------------- .../core/FM2ASTToFM3SourceConverter.java | 271 +++++++++++++++---- .../core/UnexpectedNodeContentException.java | 5 +- .../apache/freemarker/converter/Converter.java | 39 +-- .../converter/ConverterException.java | 61 ++++- .../UnconvertableLegacyFeatureException.java | 43 +++ .../converter/FM2ToFM3ConverterTest.java | 58 +++- .../converter/GenericConverterTest.java | 20 ++ 7 files changed, 418 insertions(+), 79 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java index de31532..6ff6958 100644 --- a/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java +++ b/freemarker-converter/src/main/java/freemarker/core/FM2ASTToFM3SourceConverter.java @@ -20,6 +20,7 @@ package freemarker.core; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -32,6 +33,7 @@ import org.apache.freemarker.converter.ConversionMarkers; import org.apache.freemarker.converter.ConversionMarkers.Type; import org.apache.freemarker.converter.ConverterException; import org.apache.freemarker.converter.ConverterUtils; +import org.apache.freemarker.converter.UnconvertableLegacyFeatureException; import org.apache.freemarker.core.NamingConvention; import org.apache.freemarker.core.util.FTLUtil; import org.apache.freemarker.core.util._ClassUtil; @@ -214,11 +216,11 @@ public class FM2ASTToFM3SourceConverter { } private String convertFtlHeaderParamName(String name) throws ConverterException { - name = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); - if (name.equals("attributes")) { - name = "customSettings"; + String converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + if (converted.equals("attributes")) { + converted = "customSettings"; } - return name; + return converted; } private void printNode(TemplateObject node) throws ConverterException { @@ -257,20 +259,25 @@ public class FM2ASTToFM3SourceConverter { boolean isNoParseBlock = src.startsWith(tagBeginChar + "#no", startPos); if (isNoParseBlock) { - printCoreDirStartTagParameterless(node, "noParse"); + printDirStartTagNoParamsHasNested(node, "noParse"); } print(getOnlyParam(node, ParameterRole.CONTENT, String.class)); if (isNoParseBlock) { - printCoreDirEndTag(node, NO_PARSE_FM_2_TAG_NAMES, "noParse"); + printDirEndTag(node, NO_PARSE_FM_2_TAG_NAMES, "noParse"); } } private static final ImmutableList<String> NO_PARSE_FM_2_TAG_NAMES = ImmutableList.of("noparse", "noParse"); - private void printComment(Comment node) throws UnexpectedNodeContentException { + private void printComment(Comment node) throws UnexpectedNodeContentException, UnconvertableLegacyFeatureException { print(tagBeginChar); print("#--"); - print(getOnlyParam(node, ParameterRole.CONTENT, String.class)); + String content = getOnlyParam(node, ParameterRole.CONTENT, String.class); + if (content.indexOf("-->") != -1) { + throw new UnconvertableLegacyFeatureException("You can't have a \"-->\" inside a comment.", + node.getBeginLine(), node.getBeginColumn()); + } + print(content); print("--"); print(tagEndChar); } @@ -385,13 +392,149 @@ public class FM2ASTToFM3SourceConverter { printDirBreak((BreakInstruction) node); } else if (node instanceof TrimInstruction) { printDirTOrNtOrLtOrRt((TrimInstruction) node); + } else if (node instanceof PropertySetting) { + printDirSetting((PropertySetting) node); + } else if (node instanceof StopInstruction) { + printDirStop((StopInstruction) node); + } else if (node instanceof SwitchBlock) { + printDirSwitch((SwitchBlock) node); + } else if (node instanceof Case) { + printDirCase((Case) node); + } else if (node instanceof VisitNode) { + printDirVisit((VisitNode) node); + } else if (node instanceof RecurseNode) { + printDirRecurse((RecurseNode) node); + } else if (node instanceof FallbackInstruction) { + printDirFallback((FallbackInstruction) node); } else { throw new ConverterException("Unhandled AST TemplateElement class: " + node.getClass().getName()); } } + private void printDirFallback(FallbackInstruction node) throws ConverterException { + printDirGenericNoParamsNoNested(node, "fallback"); + } + + private void printDirVisit(VisitNode node) throws ConverterException { + printDirVisitLike(node, "visit"); + } + + private void printDirRecurse(RecurseNode node) throws ConverterException { + printDirVisitLike(node, "recurse"); + } + + private void printDirVisitLike(TemplateElement node, String tagName) throws ConverterException { + assertParamCount(node, 2); + + printStartTagPartBeforeParams(node, tagName); + + Expression lastParam; + + Expression nodeExp = getParam(node, 0, ParameterRole.NODE, Expression.class); + printExp(nodeExp); + lastParam = nodeExp; + + Expression ns = getParam(node, 1, ParameterRole.NAMESPACE, Expression.class); + if (ns != null) { + printSeparatorAndWSAndExpComments(getEndPositionExclusive(lastParam), "using"); + printExp(ns); + lastParam = ns; + } + + printStartTagEnd(node, lastParam, false); + } + + private void printDirCase(Case node) throws ConverterException { + assertParamCount(node, 2); + + String tagName; + Integer subtype = getParam(node, 1, ParameterRole.AST_NODE_SUBTYPE, Integer.class); + if (subtype == Case.TYPE_CASE) { + tagName = "case"; + } else if (subtype == Case.TYPE_DEFAULT) { + tagName = "default"; + } else { + throw new UnexpectedNodeContentException(node, "Unsupported subtype {}", subtype); + } + + int pos = printStartTagPartBeforeParams(node, tagName); + + Expression value = getParam(node, 0, ParameterRole.CONDITION, Expression.class); + if (value != null) { + printExp(value); + pos = getEndPositionExclusive(value); + } + + printStartTagEnd(node, pos, false); + + printChildElements(node); + + // Element end tag is always omitted + } + + private void printDirSwitch(SwitchBlock node) throws ConverterException { + assertParamCount(node, 1); + + printStartTagPartBeforeParams(node, "switch"); + + Expression param = getOnlyParam(node, ParameterRole.VALUE, Expression.class); + printExp(param); + + printStartTagEnd(node, param, false); + + printChildElements(node); + + printDirEndTag(node, "switch"); + } + + private void printDirStop(StopInstruction node) throws ConverterException { + assertParamCount(node, 1); + + int pos = printStartTagPartBeforeParams(node, "stop"); + Expression message = getParam(node, 0, ParameterRole.MESSAGE, Expression.class); + if (message != null) { + printExp(message); + pos = getEndPositionExclusive(message); + } + printStartTagEnd(node, pos, false); + } + + private void printDirSetting(PropertySetting node) throws ConverterException { + assertParamCount(node, 2); + + int pos = printStartTagPartBeforeParams(node, "setting"); + + print(FTLUtil.escapeIdentifier(convertSettingName( + getParam(node, 0, ParameterRole.ITEM_KEY, String.class), + node))); + pos = getPositionAfterIdentifier(pos); + + pos = printSeparatorAndWSAndExpComments(pos, "="); + + Expression paramValue = getParam(node, 1, ParameterRole.ITEM_VALUE, Expression.class); + printExp(paramValue); + + printStartTagEnd(node, paramValue, false); + } + + private String convertSettingName(String name, TemplateObject node) throws ConverterException { + String converted = name.indexOf('_') == -1 ? name : ConverterUtils.snakeCaseToCamelCase(name); + + if (converted.equals("classicCompatible")) { + throw new UnconvertableLegacyFeatureException("There \"classicCompatible\" setting doesn't exist in " + + "FreeMarker 3. You have to remove it manually before conversion.", + node.getBeginLine(), node.getBeginColumn()); + } + + if (!Arrays.asList(PropertySetting.SETTING_NAMES).contains(converted)) { + throw new ConverterException("Couldn't map \"" + name + "\" to a valid FreeMarker 3 setting name " + + "(tried: " + converted + ")"); + } + return converted; + } + private void printDirTOrNtOrLtOrRt(TrimInstruction node) throws ConverterException { - int subtype= getOnlyParam(node, ParameterRole.AST_NODE_SUBTYPE, Integer.class); + int subtype = getOnlyParam(node, ParameterRole.AST_NODE_SUBTYPE, Integer.class); String tagName; if (subtype == TrimInstruction.TYPE_T) { tagName = "t"; @@ -405,11 +548,11 @@ public class FM2ASTToFM3SourceConverter { throw new UnexpectedNodeContentException(node, "Unhandled subtype {}.", subtype); } - printCoreDirStartTagParameterless(node, tagName); + printDirStartTagNoParamsNoNested(node, tagName); } private void printDirNested(BodyInstruction node) throws ConverterException { - int pos = printCoreDirStartTagBeforeParams(node, "nested"); + int pos = printStartTagPartBeforeParams(node, "nested"); int paramCnt = node.getParameterCount(); for (int paramIdx = 0; paramIdx < paramCnt; paramIdx++) { Expression passedValue = getParam(node, paramIdx, ParameterRole.PASSED_VALUE, Expression.class); @@ -423,11 +566,11 @@ public class FM2ASTToFM3SourceConverter { } private void printDirBreak(BreakInstruction node) throws ConverterException { - printCoreDirStartTagParameterless(node, "break"); + printDirStartTagNoParamsNoNested(node, "break"); } private void printDirItems(Items node) throws ConverterException { - int pos = printCoreDirStartTagBeforeParams(node, "items"); + int pos = printStartTagPartBeforeParams(node, "items"); pos = printSeparatorAndWSAndExpComments(pos, "as"); int paramCnt = node.getParameterCount(); @@ -447,7 +590,7 @@ public class FM2ASTToFM3SourceConverter { printChildElements(node); - printCoreDirEndTag(node, "items"); + printDirEndTag(node, "items"); } private void printDirListElseContainer(ListElseContainer node) throws ConverterException { @@ -455,25 +598,25 @@ public class FM2ASTToFM3SourceConverter { printDirListOrForeach((IteratorBlock) node.getChild(0), false); printDirElseOfList((ElseOfList) node.getChild(1)); - printCoreDirEndTag(node, "list"); + printDirEndTag(node, "list"); } private void printDirElseOfList(ElseOfList node) throws ConverterException { - printCoreDirStartTagParameterless(node, "else"); + printDirStartTagNoParamsHasNested(node, "else"); printChildElements(node); } private void printDirSep(Sep node) throws ConverterException { - printCoreDirStartTagParameterless(node, "sep"); + printDirStartTagNoParamsHasNested(node, "sep"); printChildElements(node); - printCoreDirEndTag(node, Collections.singleton("sep"), "sep", true); + printDirEndTag(node, Collections.singleton("sep"), "sep", true); } private void printDirListOrForeach(IteratorBlock node, boolean printEndTag) throws ConverterException { int paramCount = node.getParameterCount(); assertNodeContent(paramCount <= 3, node, "ParameterCount <= 3 was expected"); - int pos = printCoreDirStartTagBeforeParams(node, "list"); + int pos = printStartTagPartBeforeParams(node, "list"); Expression listSource = getParam(node, 0, ParameterRole.LIST_SOURCE, Expression.class); // To be future proof, we don't assume that the parameter count of list don't include the null parameters. @@ -550,7 +693,7 @@ public class FM2ASTToFM3SourceConverter { printChildElements(node); if (printEndTag) { - printCoreDirEndTag(node, LIST_FM_2_TAG_NAMES, "list", false); + printDirEndTag(node, LIST_FM_2_TAG_NAMES, "list", false); } } @@ -559,7 +702,7 @@ public class FM2ASTToFM3SourceConverter { private void printDirInclude(Include node) throws ConverterException { assertParamCount(node, 4); - printCoreDirStartTagBeforeParams(node, "include"); + printStartTagPartBeforeParams(node, "include"); Expression templateName = getParam(node, 0, ParameterRole.TEMPLATE_NAME, Expression.class); int templateNameEndPos = getEndPositionExclusive(templateName); @@ -642,7 +785,7 @@ public class FM2ASTToFM3SourceConverter { private void printDirImport(LibraryLoad node) throws ConverterException { assertParamCount(node, 2); - printCoreDirStartTagBeforeParams(node, "import"); + printStartTagPartBeforeParams(node, "import"); Expression templateName = getParam(node, 0, ParameterRole.TEMPLATE_NAME, Expression.class); printExp(templateName); @@ -656,7 +799,7 @@ public class FM2ASTToFM3SourceConverter { } private void printDirReturn(ReturnInstruction node) throws ConverterException { - printCoreDirStartTagBeforeParams(node, "return"); + printStartTagPartBeforeParams(node, "return"); Expression value = getOnlyParam(node, ParameterRole.VALUE, Expression.class); printExp(value); @@ -664,11 +807,11 @@ public class FM2ASTToFM3SourceConverter { } private void printDirFlush(FlushInstruction node) throws ConverterException { - printDirGenericParameterlessWithoutNestedContent(node, "flush"); + printDirGenericNoParamsNoNested(node, "flush"); } private void printDirNoEscape(NoEscapeBlock node) throws ConverterException { - printDirGenericParameterlessWithNestedContent(node, NO_ESCAPE_FM_2_TAG_NAMES, "noEscape"); + printDirGenericNoParamsHasNested(node, NO_ESCAPE_FM_2_TAG_NAMES, "noEscape"); } private static final ImmutableList<String> NO_ESCAPE_FM_2_TAG_NAMES = ImmutableList.of("noescape", "noEscape"); @@ -676,7 +819,7 @@ public class FM2ASTToFM3SourceConverter { private void printDirEscape(EscapeBlock node) throws ConverterException { assertParamCount(node, 2); - int pos = printCoreDirStartTagBeforeParams(node, "escape"); + int pos = printStartTagPartBeforeParams(node, "escape"); pos = getPositionAfterIdentifier(pos); print(FTLUtil.escapeIdentifier(getParam(node, 0, ParameterRole.PLACEHOLDER_VARIABLE, String.class))); @@ -689,61 +832,61 @@ public class FM2ASTToFM3SourceConverter { printChildElements(node); - printCoreDirEndTag(node, "escape"); + printDirEndTag(node, "escape"); } private void printDirCompress(CompressedBlock node) throws ConverterException { - printDirGenericParameterlessWithNestedContent(node, "compress"); + printDirGenericNoParamsHasNested(node, "compress"); } private void printDirAutoEsc(AutoEscBlock node) throws ConverterException { - printDirGenericParameterlessWithNestedContent(node, AUTO_ESC_FM_2_TAG_NAMES, "autoEsc"); + printDirGenericNoParamsHasNested(node, AUTO_ESC_FM_2_TAG_NAMES, "autoEsc"); } private static final ImmutableList<String> AUTO_ESC_FM_2_TAG_NAMES = ImmutableList.of("autoesc", "autoEsc"); private void printDirNoAutoEsc(NoAutoEscBlock node) throws ConverterException { - printDirGenericParameterlessWithNestedContent(node, NO_AUTO_ESC_FM_2_TAG_NAMES, "noAutoEsc"); + printDirGenericNoParamsHasNested(node, NO_AUTO_ESC_FM_2_TAG_NAMES, "noAutoEsc"); } private static final ImmutableList<String> NO_AUTO_ESC_FM_2_TAG_NAMES = ImmutableList.of("noautoesc", "noAutoEsc"); - private void printDirGenericParameterlessWithNestedContent(TemplateElement node, String fm3TagName) + private void printDirGenericNoParamsHasNested(TemplateElement node, String fm3TagName) throws ConverterException { - printDirGenericParameterlessWithNestedContent(node, Collections.singleton(fm3TagName), fm3TagName); + printDirGenericNoParamsHasNested(node, Collections.singleton(fm3TagName), fm3TagName); } - private void printDirGenericParameterlessWithNestedContent(TemplateElement node, + private void printDirGenericNoParamsHasNested(TemplateElement node, Collection<String> fm2TagName, String fm3TagName) throws ConverterException { assertParamCount(node, 0); - printCoreDirStartTagParameterless(node, fm3TagName); + printDirStartTagNoParamsHasNested(node, fm3TagName); printChildElements(node); - printCoreDirEndTag(node, fm2TagName, fm3TagName); + printDirEndTag(node, fm2TagName, fm3TagName); } - private void printDirGenericParameterlessWithoutNestedContent(TemplateElement node, String name) + private void printDirGenericNoParamsNoNested(TemplateElement node, String name) throws ConverterException { assertParamCount(node, 0); - printCoreDirStartTagParameterless(node, name); + printDirStartTagNoParamsNoNested(node, name); } private void printDirAttemptRecover(AttemptBlock node) throws ConverterException { assertParamCount(node, 1); // 1: The recovery block - printCoreDirStartTagParameterless(node, "attempt"); + printDirStartTagNoParamsHasNested(node, "attempt"); printNode(node.getChild(0)); assertNodeContent(node.getChild(1) instanceof RecoveryBlock, node, "child[1] should be #recover"); RecoveryBlock recoverDir = getOnlyParam(node, ParameterRole.ERROR_HANDLER, RecoveryBlock.class); - printCoreDirStartTagParameterless(recoverDir, "recover"); + printDirStartTagNoParamsHasNested(recoverDir, "recover"); printChildElements(recoverDir); // In FM2 this could be </#recover> as well, but we normalize it - printCoreDirEndTag(node, ATTEMPT_RECOVER_FM_2_TAG_NAMES, "attempt", false); + printDirEndTag(node, ATTEMPT_RECOVER_FM_2_TAG_NAMES, "attempt", false); } private static final ImmutableList<String> ATTEMPT_RECOVER_FM_2_TAG_NAMES = ImmutableList.of("attempt", "recover"); @@ -792,7 +935,7 @@ public class FM2ASTToFM3SourceConverter { printChildElements(node); - printCoreDirEndTag(node, getAssignmentDirTagName(node, 1)); + printDirEndTag(node, getAssignmentDirTagName(node, 1)); } private void printDirAssignmentCommonTagAfterLastAssignmentExp(TemplateElement node, int nsParamIdx, int pos) @@ -812,7 +955,7 @@ public class FM2ASTToFM3SourceConverter { private int printDirAssignmentCommonTagTillAssignmentExp(TemplateElement node, int scopeParamIdx) throws ConverterException { - return printCoreDirStartTagBeforeParams(node, getAssignmentDirTagName(node, scopeParamIdx)); + return printStartTagPartBeforeParams(node, getAssignmentDirTagName(node, scopeParamIdx)); } private String getAssignmentDirTagName(TemplateElement node, int scopeParamIdx) @@ -868,7 +1011,7 @@ public class FM2ASTToFM3SourceConverter { throw new UnexpectedNodeContentException(node, "Unhandled node subtype: {}", subtype); } - int pos = printCoreDirStartTagBeforeParams(node, tagName); + int pos = printStartTagPartBeforeParams(node, tagName); String assignedName = getParam(node, 0, ParameterRole.ASSIGNMENT_TARGET, String.class); print(FTLUtil.escapeIdentifier(assignedName)); @@ -935,7 +1078,7 @@ public class FM2ASTToFM3SourceConverter { printChildElements(node); - printCoreDirEndTag(node, tagName); + printDirEndTag(node, tagName); } private void printDirCustom(UnifiedCall node) throws ConverterException { @@ -1049,24 +1192,24 @@ public class FM2ASTToFM3SourceConverter { } if (conditionExp != null) { - printCoreDirStartTagBeforeParams(node, tagName); + printStartTagPartBeforeParams(node, tagName); printNode(conditionExp); printStartTagEnd(node, conditionExp, true); } else { - printCoreDirStartTagParameterless(node, tagName); + printDirStartTagNoParamsHasNested(node, tagName); } printChildElements(node); if (!(node.getParentElement() instanceof IfBlock)) { - printCoreDirEndTag(node, "if"); + printDirEndTag(node, "if"); } } private void printDirIfElseElseIfContainer(IfBlock node) throws ConverterException { printChildElements(node); - printCoreDirEndTag(node, "if"); + printDirEndTag(node, "if"); } /** @@ -1541,7 +1684,7 @@ public class FM2ASTToFM3SourceConverter { * * @return The position in the source after the printed part */ - private int printCoreDirStartTagBeforeParams(TemplateElement node, String fm3TagName) + private int printStartTagPartBeforeParams(TemplateElement node, String fm3TagName) throws ConverterException { print(tagBeginChar); print('#'); @@ -1549,18 +1692,28 @@ public class FM2ASTToFM3SourceConverter { return printWSAndExpComments(getPositionAfterTagName(node)); } - private int printCoreDirStartTagParameterless(TemplateElement node, String fm3TagName) + private int printDirStartTagNoParamsNoNested(TemplateElement node, String fm3TagName) throws ConverterException { - int pos = printCoreDirStartTagBeforeParams(node, fm3TagName); - printStartTagEnd(node, pos, true); + return printDirStartTagNoParams(node, fm3TagName, false); + } + + private int printDirStartTagNoParamsHasNested(TemplateElement node, String fm3TagName) + throws ConverterException { + return printDirStartTagNoParams(node, fm3TagName, true); + } + + private int printDirStartTagNoParams(TemplateElement node, String fm3TagName, boolean removeSlash) + throws ConverterException { + int pos = printStartTagPartBeforeParams(node, fm3TagName); + printStartTagEnd(node, pos, removeSlash); return pos + 1; } - private void printCoreDirEndTag(TemplateElement node, String tagName) throws UnexpectedNodeContentException { - printCoreDirEndTag(node, Collections.singleton(tagName), tagName); + private void printDirEndTag(TemplateElement node, String tagName) throws UnexpectedNodeContentException { + printDirEndTag(node, Collections.singleton(tagName), tagName); } - private void printCoreDirEndTag(TemplateElement node, Collection<String> fm2TagName, String fm3TagName) throws + private void printDirEndTag(TemplateElement node, Collection<String> fm2TagName, String fm3TagName) throws UnexpectedNodeContentException { if (fm2TagName.size() == 0) { throw new IllegalArgumentException("You must specify at least 1 FM2 tag names"); @@ -1570,10 +1723,10 @@ public class FM2ASTToFM3SourceConverter { "You must specify multiple FM2 tag names when the FM3 tag name (" + fm3TagName + ") contains upper case letters"); } - printCoreDirEndTag(node, fm2TagName, fm3TagName, false); + printDirEndTag(node, fm2TagName, fm3TagName, false); } - private void printCoreDirEndTag(TemplateElement node, Collection<String> fm2TagNames, String fm3TagName, + private void printDirEndTag(TemplateElement node, Collection<String> fm2TagNames, String fm3TagName, boolean optional) throws UnexpectedNodeContentException { int tagEndPos = getEndPositionInclusive(node); @@ -1661,10 +1814,10 @@ public class FM2ASTToFM3SourceConverter { */ private int printStartTagEnd(TemplateElement node, Expression lastParam, boolean trimSlash) throws ConverterException { + _NullArgumentException.check("lastParam", lastParam); return printStartTagEnd( node, - lastParam == null ? getPositionAfterTagName(node) - : getPosition(lastParam.getEndColumn() + 1, lastParam.getEndLine()), + getPosition(lastParam.getEndColumn() + 1, lastParam.getEndLine()), trimSlash); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/main/java/freemarker/core/UnexpectedNodeContentException.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/freemarker/core/UnexpectedNodeContentException.java b/freemarker-converter/src/main/java/freemarker/core/UnexpectedNodeContentException.java index c8bfe73..f7c7408 100644 --- a/freemarker-converter/src/main/java/freemarker/core/UnexpectedNodeContentException.java +++ b/freemarker-converter/src/main/java/freemarker/core/UnexpectedNodeContentException.java @@ -25,8 +25,9 @@ import org.apache.freemarker.core.util._StringUtil; public class UnexpectedNodeContentException extends ConverterException { public UnexpectedNodeContentException(TemplateObject node, String errorMessage, Object msgParam) { super("Unexpected AST content for " + _StringUtil.jQuote(node.getNodeTypeSymbol()) + " node (class: " - + node.getClass().getName() + ") " + node.getStartLocation() + ":\n" - + renderMessage(errorMessage, msgParam)); + + node.getClass().getName() + "):\n" + + renderMessage(errorMessage, msgParam), + node.getBeginLine(), node.getBeginColumn()); } private static String renderMessage(String errorMessage, Object msgParam) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/main/java/org/apache/freemarker/converter/Converter.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/org/apache/freemarker/converter/Converter.java b/freemarker-converter/src/main/java/org/apache/freemarker/converter/Converter.java index d35a34d..2b3d8eb 100644 --- a/freemarker-converter/src/main/java/org/apache/freemarker/converter/Converter.java +++ b/freemarker-converter/src/main/java/org/apache/freemarker/converter/Converter.java @@ -42,6 +42,8 @@ public abstract class Converter { public static final String PROPERTY_NAME_DESTINATION_DIRECTORY = "destinationDirectory"; public static final String CONVERSION_MARKERS_FILE_NAME = "__conversion-markers.txt"; + public static ThreadLocal<FileConversionContext> FILE_CONVERSION_CONTEXT_TLS = new ThreadLocal<>(); + private static final Logger LOG = LoggerFactory.getLogger(Converter.class); private File source; @@ -148,29 +150,34 @@ public abstract class Converter { throw new ConverterException("Failed to open file for reading: " + src, e); } try { - LOG.debug("Converting file: {}", src); - FileConversionContext ctx = null; try { - ctx = new FileConversionContext(srcStream, src, dstDir); - convertFile(ctx); - storeConversionMarkers(ctx.getConversionMarkers(), ctx); - } catch (IOException e) { - throw new ConverterException("I/O exception while converting " + _StringUtil.jQuote(src) + ".", e); - } finally { + LOG.debug("Converting file: {}", src); + FileConversionContext ctx = null; try { - if (ctx != null && ctx.outputStream != null) { - ctx.outputStream.close(); + ctx = new FileConversionContext(srcStream, src, dstDir); + FILE_CONVERSION_CONTEXT_TLS.set(ctx); + convertFile(ctx); + storeConversionMarkers(ctx.getConversionMarkers(), ctx); + } catch (IOException e) { + throw new ConverterException("I/O exception while converting " + _StringUtil.jQuote(src) + ".", e); + } finally { + try { + if (ctx != null && ctx.outputStream != null) { + ctx.outputStream.close(); + } + } catch (IOException e) { + throw new ConverterException("Failed to close destination file", e); } + } + } finally { + try { + srcStream.close(); } catch (IOException e) { - throw new ConverterException("Failed to close destination file", e); + throw new ConverterException("Failed to close file: " + src, e); } } } finally { - try { - srcStream.close(); - } catch (IOException e) { - throw new ConverterException("Failed to close file: " + src, e); - } + FILE_CONVERSION_CONTEXT_TLS.remove(); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterException.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterException.java b/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterException.java index 4a6533b..d474805 100644 --- a/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterException.java +++ b/freemarker-converter/src/main/java/org/apache/freemarker/converter/ConverterException.java @@ -19,14 +19,73 @@ package org.apache.freemarker.converter; +import org.apache.freemarker.core.util._NullArgumentException; + public class ConverterException extends Exception { + private final Integer row; + private final Integer column; + public ConverterException(String message) { this(message, null); } public ConverterException(String message, Throwable cause) { - super(message, cause); + this(message, null, null, cause); + } + + /** + * See {@link #ConverterException(String, Integer, Integer, Throwable)} + */ + public ConverterException(String message, Integer row, Integer column) { + this(message, row, column, null); + } + + /** + * @param row The 1-based row in the source file, or {@code null}. + * @param column The 1-based column in the source file, or {@code null}. + */ + public ConverterException(String message, Integer row, Integer column, Throwable cause) { + super(addLocationToMessage(message, row, column), cause); + this.row = row; + this.column = column; + } + + private static String addLocationToMessage(String message, Integer row, Integer column) { + _NullArgumentException.check("message", message); + + StringBuilder sb = new StringBuilder(); + + Converter.FileConversionContext ctx = Converter.FILE_CONVERSION_CONTEXT_TLS.get(); + if (ctx != null || row != null) { + sb.append("At "); + if (ctx != null) { + sb.append(ctx.getSourceFile()).append(':'); + } + if (row != null) { + sb.append(row).append(':'); + if (column != null) { + sb.append(column).append(':'); + } + } + sb.append(" "); + } + + sb.append(message); + return sb.toString(); } + /** + * The 1-based row in the source file, or {@code null}. + */ + public Integer getRow() { + return row; + } + + /** + * The 1-based column in the source file, or {@code null}. + */ + public Integer getColumn() { + return column; + } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/main/java/org/apache/freemarker/converter/UnconvertableLegacyFeatureException.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/main/java/org/apache/freemarker/converter/UnconvertableLegacyFeatureException.java b/freemarker-converter/src/main/java/org/apache/freemarker/converter/UnconvertableLegacyFeatureException.java new file mode 100644 index 0000000..c3a4d6e --- /dev/null +++ b/freemarker-converter/src/main/java/org/apache/freemarker/converter/UnconvertableLegacyFeatureException.java @@ -0,0 +1,43 @@ +/* + * 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.freemarker.converter; + +/** + * The legacy feature has no equivalent in the target format. + */ +public class UnconvertableLegacyFeatureException extends ConverterException { + + /** + * @param row 1-based + * @param column 1-based + */ + public UnconvertableLegacyFeatureException(String message, int row, int column) { + this(message, row, column, null); + } + + /** + * @param row 1-based + * @param column 1-based + */ + public UnconvertableLegacyFeatureException(String message, int row, int column, Throwable cause) { + super(message, row, column, cause); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java index 1243251..a76350c 100644 --- a/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java +++ b/freemarker-converter/src/test/java/org/freemarker/converter/FM2ToFM3ConverterTest.java @@ -30,6 +30,7 @@ import java.util.Properties; import org.apache.commons.io.FileUtils; import org.apache.freemarker.converter.ConverterException; import org.apache.freemarker.converter.FM2ToFM3Converter; +import org.apache.freemarker.converter.UnconvertableLegacyFeatureException; import org.freemarker.converter.test.ConverterTest; import org.junit.Test; @@ -308,9 +309,64 @@ public class FM2ToFM3ConverterTest extends ConverterTest { assertConvertedSame("a<#t>\nb"); assertConvertedSame("<#t><#nt><#lt><#rt>"); assertConvertedSame("<#t ><#nt ><#lt ><#rt >"); - assertConverted("<#t><#nt><#lt><#rt>", "<#t /><#nt /><#lt /><#rt />"); + assertConvertedSame("<#t><#nt><#lt><#rt>"); assertConvertedSame("<#ftl stripText='true'>\n\n<#macro m>\nx\n</#macro>\n"); + + assertConvertedSame("<#setting <#--1--> numberFormat <#--2--> = <#--3--> '0.0' <#--4-->>"); + assertConverted("<#setting numberFormat='0.0' />", "<#setting number_format='0.0' />"); + try { + convert("x<#setting classic_compatible=true>"); + fail(); + } catch (UnconvertableLegacyFeatureException e) { + assertEquals(1, (Object) e.getRow()); + assertEquals(2, (Object) e.getColumn()); + } + + assertConvertedSame("<#stop>"); + assertConvertedSame("<#stop />"); + assertConvertedSame("<#stop 'Reason'>"); + assertConvertedSame("<#stop <#--1--> 'Reason' <#--2-->>"); + + assertConvertedSame("" + + "<#switch x>\n" + + " <#--1-->\n" + + " <#case 1>one<#break>\n" + + " <#--2-->\n" + + " <#case 3>one<#break />\n" + + " <#case 3>fall through<#case 4>three<#break>\n" + + " <#default>def\n" + + "</#switch>"); + assertConvertedSame("" + + "<#switch x>\n" + + " <#--1-->\n" + + "</#switch>"); + assertConvertedSame("<#switch x> </#switch>"); + assertConvertedSame("<#switch x><#-- Empty --></#switch>"); + assertConverted("<#switch x> <#case 2> </#switch>", "<#switch x> <#case 2> </#switch>"); + + assertConvertedSame("<#visit node>"); + assertConvertedSame("<#visit <#--1--> node <#--2-->>"); + assertConvertedSame("<#visit node using ns>"); + assertConvertedSame("<#visit node <#--1--> using <#--2--> ns <#--3-->>"); + assertConvertedSame("<#recurse node>"); + assertConvertedSame("<#recurse <#--1--> node <#--2-->>"); + assertConvertedSame("<#recurse node using ns>"); + assertConvertedSame("<#recurse node <#--1--> using <#--2--> ns <#--3-->>"); + assertConvertedSame("<#macro m><#fallback></#macro>"); + assertConvertedSame("<#macro m><#fallback /></#macro>"); + } + + @Test + public void testLegacyDirectives() throws IOException, ConverterException { + assertConverted("<#--<#bar>-->", "<#comment><#bar></#comment>"); + try { + convert("x<#comment>--></#comment>"); + fail(); + } catch (UnconvertableLegacyFeatureException e) { + assertEquals(1, (Object) e.getRow()); + assertEquals(2, (Object) e.getColumn()); + } } @Test http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/22d3ef2e/freemarker-converter/src/test/java/org/freemarker/converter/GenericConverterTest.java ---------------------------------------------------------------------- diff --git a/freemarker-converter/src/test/java/org/freemarker/converter/GenericConverterTest.java b/freemarker-converter/src/test/java/org/freemarker/converter/GenericConverterTest.java index 23fae4b..7fcee1a 100644 --- a/freemarker-converter/src/test/java/org/freemarker/converter/GenericConverterTest.java +++ b/freemarker-converter/src/test/java/org/freemarker/converter/GenericConverterTest.java @@ -197,12 +197,28 @@ public class GenericConverterTest extends ConverterTest { assertTrue(markersFile.exists()); } + @Test + public void testLocationInException() throws IOException, ConverterException { + write(new File(srcDir, "error.txt"), "x[trigger error]", UTF_8); + + ToUpperCaseConverter converter = new ToUpperCaseConverter(); + converter.setSource(srcDir); + converter.setDestinationDirectory(dstDir); + try { + converter.execute(); + fail(); + } catch (ConverterException e) { + assertThat(e.getMessage(), containsString("error.txt:1:2: Error message")); + } + } + public static class ToUpperCaseConverter extends Converter { @Override protected void convertFile(FileConversionContext ctx) throws ConverterException, IOException { String content = IOUtils.toString(ctx.getSourceStream(), StandardCharsets.UTF_8); ctx.setDestinationFileName(ctx.getSourceFileName() + ".uc"); + if (content.contains("[trigger warn]")) { ctx.getConversionMarkers().markInSource( 1, 2, ConversionMarkers.Type.WARN, "Warn message"); @@ -211,6 +227,10 @@ public class GenericConverterTest extends ConverterTest { ctx.getConversionMarkers().markInDestination( 1, 2, ConversionMarkers.Type.TIP, "Tip message"); } + if (content.contains("[trigger error]")) { + throw new ConverterException("Error message", 1, 2); + } + IOUtils.write(content.toUpperCase(), ctx.getDestinationStream(), StandardCharsets.UTF_8); }
