This is an automated email from the ASF dual-hosted git repository. ddekany pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/freemarker-docgen.git
commit f5f9661464a77965eae4aab16fc479203c7b5f93 Author: ddekany <[email protected]> AuthorDate: Sun Jan 24 22:29:15 2021 +0100 Reworked setting validation API-s, and some other cleanup of old code. --- freemarker-docgen-core/pom.xml | 17 + .../freemarker/docgen/core/CJSONInterpreter.java | 42 +- .../freemarker/docgen/core/DocgenException.java | 2 +- ...ocgenException.java => DocgenTagException.java} | 24 +- .../main/java/org/freemarker/docgen/core/Logo.java | 26 +- .../org/freemarker/docgen/core/SettingName.java | 82 ++ .../org/freemarker/docgen/core/SettingUtils.java | 340 +++++++++ .../java/org/freemarker/docgen/core/Transform.java | 835 +++++++-------------- .../freemarker/docgen/core/templates/footer.ftlh | 6 +- .../freemarker/docgen/core/templates/header.ftlh | 4 +- .../docgen/core/templates/node-handlers.ftlh | 2 +- .../freemarker/docgen/core/SettingNameTest.java} | 25 +- .../freemarker/docgen/core/SettingUtilsTest.java | 152 ++++ 13 files changed, 910 insertions(+), 647 deletions(-) diff --git a/freemarker-docgen-core/pom.xml b/freemarker-docgen-core/pom.xml index 84bd7e7..952fccc 100644 --- a/freemarker-docgen-core/pom.xml +++ b/freemarker-docgen-core/pom.xml @@ -112,6 +112,23 @@ <artifactId>commons-text</artifactId> <version>1.9</version> </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <version>30.1-jre</version> + </dependency> + <dependency> + <groupId>org.junit.jupiter</groupId> + <artifactId>junit-jupiter-engine</artifactId> + <version>5.7.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.hamcrest</groupId> + <artifactId>hamcrest</artifactId> + <version>2.2</version> + <scope>test</scope> + </dependency> </dependencies> <build> diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/CJSONInterpreter.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/CJSONInterpreter.java index 97aca12..543aea6 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/CJSONInterpreter.java +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/CJSONInterpreter.java @@ -483,27 +483,33 @@ final class CJSONInterpreter { /** * Returns the type-name of a value according to the CJSON language. */ - public static String cjsonTypeOf(Object value) { - if (value instanceof String) { + public static String cjsonTypeNameOfValue(Object value) { + return cjsonTypeNameForClass(value != null ? value.getClass() : null); + } + + public static String cjsonTypeNameForClass(Class<?> cl) { + if (String.class.isAssignableFrom(cl)) { return "string"; - } else if (value instanceof Number) { - return "number"; - } else if (value instanceof Boolean) { + } else if (Integer.class.isAssignableFrom(cl)) { + return "int"; + } else if (Long.class.isAssignableFrom(cl)) { + return "long"; + } else if (Double.class.isAssignableFrom(cl)) { + return "double"; + } else if (BigDecimal.class.isAssignableFrom(cl)) { + return "big-decimal"; + } else if (Boolean.class.isAssignableFrom(cl)) { return "boolean"; - } else if (value instanceof List<?>) { + } else if (List.class.isAssignableFrom(cl)) { return "list"; - } else if (value instanceof LinkedHashMap<?, ?>) { + } else if (LinkedHashMap.class.isAssignableFrom(cl)) { + return "map (order keeping)"; + } else if (Map.class.isAssignableFrom(cl)) { return "map"; - } else if (value instanceof Map<?, ?>) { - return "map (unordered)"; - } else if (value instanceof FunctionCall) { + } else if (FunctionCall.class.isAssignableFrom(cl)) { return "function call"; } else { - if (value != null) { - return value.getClass().getName(); - } else { - return "null"; - } + return cl != null ? cl.getName() : "null"; } } @@ -643,7 +649,7 @@ final class CJSONInterpreter { if (keyFunc != o1) { throw newError( "The key must be a String, but it is a(n) " - + cjsonTypeOf(o1) + ".", keyP); + + cjsonTypeNameOfValue(o1) + ".", keyP); } else { throw newError( "You can't use the function here, " @@ -733,7 +739,7 @@ final class CJSONInterpreter { throw newError( "This expression should be either a string " + "or a map, but it is a(n) " - + cjsonTypeOf(o1) + ".", keyP); + + cjsonTypeNameOfValue(o1) + ".", keyP); } } else { if (o1 instanceof Map) { @@ -748,7 +754,7 @@ final class CJSONInterpreter { } else { throw newError( "Function doesn't evalute to a map, but " - + "to " + cjsonTypeOf(o1) + + "to " + cjsonTypeNameOfValue(o1) + ", so it can't be merged into the map.", keyP); } diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java index 3ec8229..e3521fd 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java @@ -21,7 +21,7 @@ package org.freemarker.docgen.core; /** * Exception that is docgen-specific. */ -public class DocgenException extends Exception { +public class DocgenException extends RuntimeException { public DocgenException(String message) { super(message); diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenTagException.java similarity index 61% copy from freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java copy to freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenTagException.java index 3ec8229..6d7ed81 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenTagException.java @@ -16,20 +16,22 @@ * specific language governing permissions and limitations * under the License. */ + package org.freemarker.docgen.core; +import freemarker.core.Environment; +import freemarker.template.TemplateException; + /** - * Exception that is docgen-specific. + * Exception thrown by docgen tag-s that are inside the XML text. As such, it's treated as the mistake of the document + * author (as opposed to an internal error). */ -public class DocgenException extends Exception { - - public DocgenException(String message) { - super(message); +public class DocgenTagException extends TemplateException { + public DocgenTagException(String description, Environment env) { + super(description, env); } - - public DocgenException( - String message, Throwable cause) { - super(message, cause); + + public DocgenTagException(String description, Throwable cause, Environment env) { + super(description, cause, env); } - -} \ No newline at end of file +} diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Logo.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Logo.java index 554317c..4abfd3d 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Logo.java +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Logo.java @@ -22,32 +22,30 @@ package org.freemarker.docgen.core; /** Model for a logo shown */ public class Logo { - private String src; + private final String src; private String href; - private String alt; - + private final String alt; + + public Logo(String src, String href, String alt) { + this.src = src; + this.href = href; + this.alt = alt; + } + public String getSrc() { return src; } - public void setSrc(String src) { - this.src = src; - } - public String getHref() { return href; } - + public void setHref(String href) { this.href = href; } - + public String getAlt() { return alt; } - - public void setAlt(String alt) { - this.alt = alt; - } - + } diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java new file mode 100644 index 0000000..78bb341 --- /dev/null +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingName.java @@ -0,0 +1,82 @@ +/* + * 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.freemarker.docgen.core; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; + +final class SettingName { + private final File parentFile; + private final SettingName parent; + private final Object key; + + public SettingName(File parentFile, SettingName parent, Object key) { + this.parentFile = parentFile; + this.parent = parent; + this.key = key; + } + + static SettingName topLevel(File parentFile, String simpleName) { + return new SettingName(parentFile, null, simpleName); + } + + SettingName subKey(Object key) { + return new SettingName(null, this, key); + } + + SettingName subKey(Object... keys) { + return new SettingName(null,this, subKey(Arrays.asList(keys))); + } + + SettingName subKey(List<Object> keys) { + SettingName result = this; + for (Object key : keys) { + result = new SettingName(null, result, key); + } + return result; + } + + Optional<File> getContainingFile() { + return parent != null ? parent.getContainingFile() : Optional.ofNullable(parentFile); + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + appendName(sb); + return sb.toString(); + } + + private void appendName(StringBuilder sb) { + if (parent != null) { + parent.appendName(sb); + } + if (key instanceof String) { + if (sb.length() != 0) { + sb.append('.'); + } + sb.append(key); + } else { + sb.append('[').append(key).append(']'); + } + } +} diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java new file mode 100644 index 0000000..c53d905 --- /dev/null +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/SettingUtils.java @@ -0,0 +1,340 @@ +/* + * 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.freemarker.docgen.core; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Sets; + +import freemarker.template.utility.StringUtil; + +final class SettingUtils { + private SettingUtils() { + throw new AssertionError(); + } + + static DocgenException newCfgFileException(SettingName settingName, String desc) { + return newCfgFileException(settingName, desc, null); + } + + static DocgenException newCfgFileException(SettingName settingName, String desc, Throwable cause) { + StringBuilder sb = new StringBuilder(); + sb.append("Wrong configuration"); + if (settingName != null) { + sb.append(" setting \"").append(settingName).append("\""); + } + settingName.getContainingFile().ifPresent(containingFile -> sb.append(" in file \"").append(containingFile.getAbsolutePath()).append("\"")); + sb.append(":\n"); + sb.append(desc); + return new DocgenException(sb.toString(), cause); + } + + @SuppressWarnings("unchecked") + static <K, V> Map<K, V> castSettingToMap( + SettingName settingName, Object settingValue, + Class<K> keyClass, Class<V> valueClass) { + return castSettingToMap(settingName, settingValue, keyClass, valueClass, false); + } + + @SuppressWarnings("unchecked") + static <K, V> Map<K, V> castSettingToMap( + SettingName settingName, Object settingValue, + Class<K> keyClass, Class<V> valueClass, boolean allowNullValueInMap) { + return (Map<K, V>) castSetting( + settingName, settingValue, + Map.class, + new MapEntryType(keyClass, valueClass, allowNullValueInMap)); + } + + @SuppressWarnings("unchecked") + static <T> List<T> castSettingToList( + SettingName settingName, + Object settingValue, Class<T> elementClass) { + return castSetting( + settingName, settingValue, + false, + List.class, new ListItemType(elementClass) + ); + } + + static <T> T castSetting(SettingName settingName, Object settingValue, Class<T> valueType) { + return castSetting(settingName, settingValue, false, valueType); + } + + /** + * Same as {@link #castSetting(List, Object, boolean, Class, List)} with {@code optional} {@code false}. + */ + static <T> T castSetting( + SettingName settingName, Object settingValue, Class<T> valueType, + ContainedValueType... containedValueTypes) { + return castSetting(settingName, settingValue, false, valueType, containedValueTypes); + } + + /** + * @param valueType + * The expected type of the value (on the top-level, if it's a container) + * @param containedValueTypes + * The expected type of the contained values, and of the values contained inside them, and so on. (This is + * separate from {@code valueType} because Java can't match s generic return type with the type of the first + */ + static <T> T castSetting( + SettingName settingName, Object settingValue, + boolean optional, + Class<T> valueType, ContainedValueType... containedValueTypes) { + if (settingValue == null) { + if (optional) { + return null; + } + throw newNullSettingValueException(settingName); + } + if (!valueType.isInstance(settingValue)) { + System.out.println("BAD VALUE: " + settingValue); //!!T + throw newBadSettingValueTypeException(settingName, valueType, settingValue); + } + + checkContainedValueTypes(settingName, settingValue, containedValueTypes); + + return (T) settingValue; + } + + static void checkContainedValueTypes( + SettingName settingName, Object settingValue, + ContainedValueType... containedValueTypes) { + if (containedValueTypes.length == 0) { + return; + } + checkContainedValueTypes(settingName, settingValue, new ArrayList<>(containedValueTypes.length), + containedValueTypes); + } + + private static void checkContainedValueTypes( + SettingName settingName, Object containerValue, + List<Object> checkedContainedSettingNameTail, ContainedValueType... containedValueTypes) { + if (checkedContainedSettingNameTail.size() == containedValueTypes.length || containerValue == null) { + return; + } + + Class<? extends Object> containerClass = containerValue.getClass(); + ContainedValueType containedValueType = containedValueTypes[checkedContainedSettingNameTail.size()]; + checkContainerClassIsValidContainedValueType(containerClass, containedValueType); + if (containedValueType instanceof ListItemType) { + int listElementIndex = 0; + for (Object listElement : ((List<?>) containerValue)) { + if (listElement == null) { + if (!containedValueType.allowNullValue) { + throw newNullSettingValueException( + settingName.subKey(checkedContainedSettingNameTail).subKey(listElementIndex)); + } + } else if (!containedValueType.valueType.isInstance(listElement)) { + throw newBadSettingValueTypeException( + settingName.subKey(checkedContainedSettingNameTail).subKey(listElementIndex), + containedValueType.valueType, listElement); + } + + checkedContainedSettingNameTail.add(listElementIndex); + try { + checkContainedValueTypes( + settingName, listElement, checkedContainedSettingNameTail, + containedValueTypes); + } finally { + checkedContainedSettingNameTail.remove(checkedContainedSettingNameTail.size() - 1); + } + listElementIndex++; + } + } else if (containedValueType instanceof MapEntryType) { + MapEntryType mapEntryType = (MapEntryType) containedValueType; + for (Map.Entry<?, ?> mapEntry : ((Map<?, ?>) containerValue).entrySet()) { + Object entryKey = mapEntry.getKey(); + if (entryKey == null) { + throw newCfgFileException( + settingName, "Null keys aren't allowed in this setting value."); + } + Class<?> keyType = mapEntryType.keyType; + if (!keyType.isInstance(entryKey)) { + throw newCfgFileException( + settingName.subKey(checkedContainedSettingNameTail), // Don't add the key. + "Expected key type " + CJSONInterpreter.cjsonTypeNameForClass(keyType) + + ", but key was of type " + CJSONInterpreter.cjsonTypeNameOfValue(entryKey)); + } + + Object entryValue = mapEntry.getValue(); + if (entryValue == null) { + if (!containedValueType.allowNullValue) { + throw newNullSettingValueException( + settingName.subKey(checkedContainedSettingNameTail).subKey(entryKey)); + } + } else if (!containedValueType.valueType.isInstance(entryValue)) { + throw newBadSettingValueTypeException( + settingName.subKey(checkedContainedSettingNameTail).subKey(entryKey), + containedValueType.valueType, entryValue); + } + + checkedContainedSettingNameTail.add(entryKey); + try { + checkContainedValueTypes( + settingName, entryValue, checkedContainedSettingNameTail, + containedValueTypes); + } finally { + checkedContainedSettingNameTail.remove(checkedContainedSettingNameTail.size() - 1); + } + } + if (mapEntryType.validateKeys) { + checkMapKeys(settingName, (Map) containerValue, mapEntryType.requiredKeys, mapEntryType.optionalKeys); + } + } else { + throw new AssertionError(); + } + } + + private static void checkContainerClassIsValidContainedValueType( + Class<?> containerClass, ContainedValueType containedValueType) { + if (!containedValueType.isValidContainerClass(containerClass)) { + throw new IllegalArgumentException( + containedValueType.getClass().getSimpleName() + + " is not fitting for provided container value class, " + + containerClass.getSimpleName() + "."); + } + } + + private static DocgenException newBadSettingValueTypeException(SettingName settingName, Class<?> expectedValueType, + Object settingValue) throws + DocgenException { + return newCfgFileException( + settingName, + "Setting value should be a(n) " + CJSONInterpreter.cjsonTypeNameForClass(expectedValueType) + ", " + + "but was a(n) " + CJSONInterpreter.cjsonTypeNameOfValue(settingValue) + "."); + } + + private static DocgenException newNullSettingValueException(SettingName settingName) { + return newCfgFileException( + settingName, + "Setting is required but wasn't set (or was set to null)."); + } + + private static <T> void checkMapKeys( + SettingName settingName, Map<T, ?> value, + Set<T> requiredKeys, Set<T> optionalKeys) { + Set<T> mapKeySet = value.keySet(); + for (T key : mapKeySet) { + if (!requiredKeys.contains(key) && !optionalKeys.contains(key)) { + throw newCfgFileException(settingName, + "Unsupported key in the map value: " + StringUtil.jQuote(key) + ". Valid keys are: " + + Sets.union(requiredKeys, optionalKeys).stream() + .sorted() + .map(it -> StringUtil.jQuote(it)) + .collect(Collectors.joining(", "))); + } + } + for (T requiredKey : requiredKeys) { + if (!mapKeySet.contains(requiredKey)) { + throw newCfgFileException(settingName, "Required key is missing from the map value: " + requiredKey); + } + } + } + + abstract static class ContainedValueType { + private final Class<?> valueType; + private final boolean allowNullValue; + + private ContainedValueType(Class<?> valueType, boolean allowNullValue) { + this.valueType = Objects.requireNonNull(valueType); + this.allowNullValue = allowNullValue; + } + + public abstract boolean isValidContainerClass(Class<?> containerClass); + } + + final static class ListItemType extends ContainedValueType { + public ListItemType(Class<?> valueType) { + this(valueType, false); + } + + public ListItemType(Class<?> valueType, boolean allowNullValue) { + super(valueType, allowNullValue); + } + + @Override + public boolean isValidContainerClass(Class<?> containerClass) { + return List.class.isAssignableFrom(containerClass); + } + } + + final static class MapEntryType<T> extends ContainedValueType { + private final Class<T> keyType; + private final boolean validateKeys; + private final Set<T> requiredKeys; + private final Set<T> optionalKeys; + + public MapEntryType(Class<T> keyType, Class<?> valueType) { + this(keyType, valueType, false); + } + + public MapEntryType(Class<T> keyType, Class<?> valueType, boolean allowNullValue) { + this(keyType, false, Collections.emptySet(), Collections.emptySet(), valueType, allowNullValue); + } + + public MapEntryType( + Class<T> keyType, Set<T> requiredKeys, + Class<?> valueType) { + this(keyType, true, requiredKeys, Collections.emptySet(), valueType, false); + } + + public MapEntryType( + Class<T> keyType, Set<T> requiredKeys, + Class<?> valueType, boolean allowNullValue) { + this(keyType, true, requiredKeys, Collections.emptySet(), valueType, allowNullValue); + } + + public MapEntryType( + Class<T> keyType, Set<T> requiredKeys, Set<T> optionalKeys, + Class<?> valueType) { + this(keyType, true, requiredKeys, optionalKeys, valueType, false); + } + + public MapEntryType( + Class<T> keyType, Set<T> requiredKeys, Set<T> optionalKeys, + Class<?> valueType, boolean allowNullValue) { + this(keyType, true, requiredKeys, optionalKeys, valueType, allowNullValue); + } + + private MapEntryType( + Class<T> keyType, boolean validateKeys, Set<T> requiredKeys, Set<T> optionalKeys, + Class<?> valueType, boolean allowNullValue) { + super(valueType, allowNullValue); + this.keyType = Objects.requireNonNull(keyType); + this.validateKeys = validateKeys; + this.requiredKeys = Objects.requireNonNull(requiredKeys); + this.optionalKeys = Objects.requireNonNull(optionalKeys); + } + + @Override + public boolean isValidContainerClass(Class<?> containerClass) { + return Map.class.isAssignableFrom(containerClass); + } + } + +} diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java index 204e492..37da9ab 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java +++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/Transform.java @@ -19,6 +19,7 @@ package org.freemarker.docgen.core; import static org.freemarker.docgen.core.DocBook5Constants.*; +import static org.freemarker.docgen.core.SettingUtils.*; import java.io.BufferedReader; import java.io.File; @@ -30,6 +31,7 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -43,11 +45,13 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Objects; import java.util.Set; import java.util.SortedMap; import java.util.TimeZone; import java.util.TreeMap; import java.util.regex.Pattern; +import java.util.stream.Collectors; import org.w3c.dom.Document; import org.w3c.dom.Element; @@ -55,6 +59,11 @@ import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.xml.sax.SAXException; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; + import freemarker.cache.ClassTemplateLoader; import freemarker.cache.FileTemplateLoader; import freemarker.cache.MultiTemplateLoader; @@ -117,6 +126,13 @@ public final class Transform { static final String SETTING_LOGO_KEY_SRC = "src"; static final String SETTING_LOGO_KEY_ALT = "alt"; static final String SETTING_LOGO_KEY_HREF = "href"; + static final Set<String> SETTING_LOGO_MAP_KEYS; + static { + SETTING_LOGO_MAP_KEYS = new LinkedHashSet<>(); + SETTING_LOGO_MAP_KEYS.add(SETTING_LOGO_KEY_SRC); + SETTING_LOGO_MAP_KEYS.add(SETTING_LOGO_KEY_ALT); + SETTING_LOGO_MAP_KEYS.add(SETTING_LOGO_KEY_HREF); + } static final String SETTING_SIDE_TOC_LOGOS = "sideTOCLogos"; static final String SETTING_TABS = "tabs"; static final String SETTING_SECONDARY_TABS = "secondaryTabs"; @@ -316,7 +332,7 @@ public final class Transform { /** Elements for which an id attribute automatically added if missing */ private static final Set<String> GUARANTEED_ID_ELEMENTS; static { - Set<String> idAttElems = new HashSet<String>(); + Set<String> idAttElems = new HashSet<>(); for (String elemName : DOCUMENT_STRUCTURE_ELEMENTS) { idAttElems.add(elemName); @@ -335,7 +351,7 @@ public final class Transform { */ private static final Set<String> PREFACE_LIKE_ELEMENTS; static { - Set<String> sinlgeFileElems = new HashSet<String>(); + Set<String> sinlgeFileElems = new HashSet<>(); sinlgeFileElems.add(E_PREFACE); @@ -350,6 +366,8 @@ public final class Transform { // ------------------------------------------------------------------------- // Settings: + private File cfgFile; + private File destDir; private File srcDir; @@ -370,11 +388,9 @@ public final class Transform { private Set<String> removeNodesWhenOnline; /** Element types for which a new output file is created */ - private DocumentStructureRank lowestFileElemenRank - = DocumentStructureRank.SECTION1; + private DocumentStructureRank lowestFileElemenRank = DocumentStructureRank.SECTION1; - private DocumentStructureRank lowestPageTOCElemenRank - = DocumentStructureRank.SECTION3; + private DocumentStructureRank lowestPageTOCElemenRank = DocumentStructureRank.SECTION3; private int maxTOFDisplayDepth = Integer.MAX_VALUE; @@ -402,24 +418,24 @@ public final class Transform { private boolean printProgress; - private LinkedHashMap<String, String> internalBookmarks = new LinkedHashMap<>(); - private LinkedHashMap<String, String> externalBookmarks = new LinkedHashMap<>(); - private Map<String, Map<String, String>> footerSiteMap; + private final LinkedHashMap<String, String> internalBookmarks = new LinkedHashMap<>(); + private final LinkedHashMap<String, String> externalBookmarks = new LinkedHashMap<>(); + private Map<String, Map<String, String>> footerSiteMap = new LinkedHashMap<>();; - private Map<String, Object> customVariablesFromSettingsFile = new HashMap<>(); - private Map<String, Object> customVariableOverrides = new HashMap<>(); + private final Map<String, Object> customVariablesFromSettingsFile = new HashMap<>(); + private final Map<String, Object> customVariableOverrides = new HashMap<>(); - private Map<String, String> insertableFilesFromSettingsFile = new HashMap<>(); - private Map<String, String> insertableFilesOverrides = new HashMap<>(); + private final Map<String, String> insertableFilesFromSettingsFile = new HashMap<>(); + private final Map<String, String> insertableFilesOverrides = new HashMap<>(); - private LinkedHashMap<String, String> tabs = new LinkedHashMap<>(); + private final LinkedHashMap<String, String> tabs = new LinkedHashMap<>(); - private Map<String, Map<String, String>> secondaryTabs; - private Map<String, Map<String, String>> socialLinks; + private final Map<String, Map<String, String>> secondaryTabs = new LinkedHashMap<>(); + private final Map<String, Map<String, String>> socialLinks = new LinkedHashMap<>(); private Logo logo; - private List<Logo> sideTOCLogos; + private final List<Logo> sideTOCLogos = new ArrayList<>(); private String copyrightHolder; private String copyrightHolderSite; @@ -428,20 +444,20 @@ public final class Transform { private String copyrightComment; private String copyrightJavaComment; - private Map<String, Map<String, String>> seoMeta; + private final Map<String, Map<String, String>> seoMeta = new LinkedHashMap(); - private DocgenValidationOptions validationOps - = new DocgenValidationOptions(); + private DocgenValidationOptions validationOps = new DocgenValidationOptions(); + + String eclipseLinkTo; // ------------------------------------------------------------------------- // Global transformation state: private boolean executed; - private Map<String, String> olinks = new HashMap<String, String>(); + private Map<String, String> olinks = new HashMap<>(); private Map<String, List<NodeModel>> primaryIndexTermLookup; - private Map<String, SortedMap<String, List<NodeModel>>> - secondaryIndexTermLookup; + private Map<String, SortedMap<String, List<NodeModel>>> secondaryIndexTermLookup; private Map<String, Element> elementsById; private List<TOCNode> tocNodes; private List<String> indexEntries; @@ -458,12 +474,14 @@ public final class Transform { private DocgenLogger logger = new DocgenLogger() { + @Override public void info(String message) { if (printProgress) { System.out.println(message); } } + @Override public void warning(String message) { if (printProgress) { System.out.println("Warning:" + message); @@ -514,9 +532,8 @@ public final class Transform { // Load configuration file: File templatesDir = null; - String eclipseLinkTo = null; - File cfgFile = new File(srcDir, FILE_SETTINGS); + cfgFile = new File(srcDir, FILE_SETTINGS); if (cfgFile.exists()) { Map<String, Object> cfg; try { @@ -527,181 +544,128 @@ public final class Transform { } for (Entry<String, Object> cfgEnt : cfg.entrySet()) { - final String settingName = cfgEnt.getKey(); + final String topSettingName = cfgEnt.getKey(); + final SettingName settingName = SettingName.topLevel(cfgFile, topSettingName); final Object settingValue = cfgEnt.getValue(); - if (settingName.equals(SETTING_IGNORED_FILES)) { - List<String> patterns = castSettingToStringList(cfgFile, settingName, settingValue); - for (String pattern : patterns) { - ignoredFilePathPatterns.add(FileUtil.globToRegexp(pattern)); - } - } else if (settingName.equals(SETTING_OLINKS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String name = ent.getKey(); - String target = castSettingValueMapValueToString( - cfgFile, settingName, ent.getValue()); - olinks.put(name, target); - } - } else if (settingName.equals(SETTING_INTERNAL_BOOKMARKS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String name = ent.getKey(); - String target = castSettingValueMapValueToString( - cfgFile, settingName, ent.getValue()); - internalBookmarks.put(name, target); - } + if (topSettingName.equals(SETTING_IGNORED_FILES)) { + castSettingToList(settingName, settingValue, String.class).forEach( + pattern -> ignoredFilePathPatterns.add(FileUtil.globToRegexp(pattern))); + } else if (topSettingName.equals(SETTING_OLINKS)) { + olinks.putAll( + castSettingToMap(settingName, settingValue, String.class, String.class)); + } else if (topSettingName.equals(SETTING_INTERNAL_BOOKMARKS)) { + internalBookmarks.putAll( + castSettingToMap(settingName, settingValue, String.class, String.class)); // Book-mark targets will be checked later, when the XML // document is already loaded. - } else if (settingName.equals(SETTING_EXTERNAL_BOOKMARKS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String name = ent.getKey(); - String target = castSettingValueMapValueToString( - cfgFile, settingName, ent.getValue()); - externalBookmarks.put(name, target); - } - } else if (settingName.equals(SETTING_LOGO)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - logo = castMapToLogo(cfgFile, settingName, m); - } else if (settingName.equals(SETTING_SIDE_TOC_LOGOS)) { - List<Map<String, Object>> listOfMaps - = castSettingToListOfMapsWithStringKeys(cfgFile, settingName, settingValue); - sideTOCLogos = new ArrayList<>(); + } else if (topSettingName.equals(SETTING_EXTERNAL_BOOKMARKS)) { + externalBookmarks.putAll( + castSettingToMap(settingName, settingValue, String.class, String.class)); + } else if (topSettingName.equals(SETTING_LOGO)) { + logo = castMapToLogo(settingName, settingValue); + } else if (topSettingName.equals(SETTING_SIDE_TOC_LOGOS)) { + List<Map<String, Object>> listOfMaps = castSetting( + settingName, settingValue, + List.class, + new ListItemType(Map.class), + new MapEntryType<>(String.class, Object.class)); for (int i = 0; i < listOfMaps.size(); i++) { - Map<String, Object> map = listOfMaps.get(i); - sideTOCLogos.add(castMapToLogo(cfgFile, settingName + "[" + i + "]", map)); + sideTOCLogos.add(castMapToLogo(settingName.subKey(i), listOfMaps.get(i))); } - } else if (settingName.equals(SETTING_COPYRIGHT_HOLDER)) { - copyrightHolder = castSettingToString(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_COPYRIGHT_HOLDER_SITE)) { - copyrightHolderSite = castSettingToString(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_COPYRIGHT_START_YEAR)) { - copyrightStartYear = castSettingToInt(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_COPYRIGHT_SUFFIX)) { - copyrightSuffix = castSettingToString(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_COPYRIGHT_COMMENT_FILE)) { - copyrightComment = StringUtil.chomp(getFileContentForSetting(cfgFile, settingName, settingValue)); + } else if (topSettingName.equals(SETTING_COPYRIGHT_HOLDER)) { + copyrightHolder = castSetting(settingName, settingValue, String.class); + } else if (topSettingName.equals(SETTING_COPYRIGHT_HOLDER_SITE)) { + copyrightHolderSite = castSetting(settingName, settingValue, String.class); + } else if (topSettingName.equals(SETTING_COPYRIGHT_START_YEAR)) { + copyrightStartYear = castSetting(settingName, settingValue, Integer.class); + } else if (topSettingName.equals(SETTING_COPYRIGHT_SUFFIX)) { + copyrightSuffix = castSetting(settingName, settingValue, String.class); + } else if (topSettingName.equals(SETTING_COPYRIGHT_COMMENT_FILE)) { + copyrightComment = + StringUtil.chomp(getFileContentForSetting(settingName, settingValue)); String eol = TextUtil.detectEOL(copyrightComment, "\n"); StringBuilder sb = new StringBuilder("/*").append(eol); new BufferedReader(new StringReader(copyrightComment)).lines() .forEach(s -> sb.append(" * ").append(s).append(eol)); sb.append(" */"); copyrightJavaComment = sb.toString(); - } else if (settingName.equals(SETTING_SEO_META)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - seoMeta = new LinkedHashMap<>(); - for (Entry<String, Object> ent : m.entrySet()) { - String k = ent.getKey(); - Map<String, String> v = castSettingValueMapValueToMapOfStringString( - cfgFile, settingName, ent.getValue(), - null, SETTING_SEO_META_KEYS); - seoMeta.put(k, v); - } - } else if (settingName.equals(SETTING_CUSTOM_VARIABLES)) { + } else if (topSettingName.equals(SETTING_SEO_META)) { + this.seoMeta.putAll( + castSetting( + settingName, settingValue, + Map.class, + new MapEntryType<>(String.class, Map.class), + new MapEntryType<>( + String.class, Collections.emptySet(), SETTING_SEO_META_KEYS, + String.class))); + } else if (topSettingName.equals(SETTING_CUSTOM_VARIABLES)) { customVariablesFromSettingsFile.putAll( - castSettingToMapWithStringKeys(cfgFile, settingName, settingValue)); - } else if (settingName.equals(SETTING_INSERTABLE_FILES)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String value = castSettingValueMapValueToString(cfgFile, settingName, ent.getValue()); - if (insertableFilesFromSettingsFile.put(ent.getKey(), value) != null) { - throw new DocgenException( - "Duplicate key " + StringUtil.jQuote(ent.getKey()) + " in " - + SETTING_INSERTABLE_FILES + "."); - } - } - } else if (settingName.equals(SETTING_TABS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String k = ent.getKey(); - String v = castSettingValueMapValueToString(cfgFile, settingName, ent.getValue()); - tabs.put(k, v); - } - } else if (settingName.equals(SETTING_SECONDARY_TABS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - secondaryTabs = new LinkedHashMap<>(); - for (Entry<String, Object> ent : m.entrySet()) { - String k = ent.getKey(); - Map<String, String> v = castSettingValueMapValueToMapOfStringString( - cfgFile, settingName, ent.getValue(), - COMMON_LINK_KEYS, null); - secondaryTabs.put(k, v); - } - } else if (settingName.equals(SETTING_SOCIAL_LINKS)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - socialLinks = new LinkedHashMap<>(); - for (Entry<String, Object> ent : m.entrySet()) { - String entName = ent.getKey(); - Map<String, String> entValue = castSettingValueMapValueToMapOfStringString( - cfgFile, settingName, ent.getValue(), - COMMON_LINK_KEYS, null); - socialLinks.put(entName, entValue); - } - } else if (settingName.equals(SETTING_FOOTER_SITEMAP)) { - // TODO Check value in more details - footerSiteMap = (Map) castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - }else if (settingName.equals(SETTING_VALIDATION)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, SETTING_VALIDATION, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String name = ent.getKey(); - if (name.equals( - SETTING_VALIDATION_PROGRAMLISTINGS_REQ_ROLE)) { - validationOps.setProgramlistingRequiresRole( - caseSettingToBoolean( - cfgFile, - settingName + "." + name, - ent.getValue())); - } else if (name.equals( - SETTING_VALIDATION_PROGRAMLISTINGS_REQ_LANG)) { - validationOps.setProgramlistingRequiresLanguage( - caseSettingToBoolean( - cfgFile, - settingName + "." + name, - ent.getValue())); - } else if (name.equals( - SETTING_VALIDATION_OUTPUT_FILES_CAN_USE_AUTOID) + // Allow null values in the Map, as the caller can override them. + castSettingToMap(settingName, settingValue, String.class, Object.class, true)); + } else if (topSettingName.equals(SETTING_INSERTABLE_FILES)) { + insertableFilesFromSettingsFile.putAll( + // Allow null values in the Map, as the caller can override them. + castSettingToMap(settingName, settingValue, String.class, String.class, true)); + } else if (topSettingName.equals(SETTING_TABS)) { + tabs.putAll( + castSettingToMap(settingName, settingValue, String.class, String.class)); + } else if (topSettingName.equals(SETTING_SECONDARY_TABS)) { + secondaryTabs.putAll( + castSetting( + settingName, settingValue, + Map.class, + new MapEntryType(String.class, Map.class), + new MapEntryType(String.class, COMMON_LINK_KEYS, String.class))); + } else if (topSettingName.equals(SETTING_SOCIAL_LINKS)) { + socialLinks.putAll( + castSetting( + settingName, settingValue, + Map.class, + new MapEntryType(String.class, Map.class), + new MapEntryType(String.class, COMMON_LINK_KEYS, String.class))); + } else if (topSettingName.equals(SETTING_FOOTER_SITEMAP)) { + footerSiteMap.putAll( + castSetting( + settingName, settingValue, + Map.class, + new MapEntryType(String.class, Map.class), + new MapEntryType(String.class, String.class))); + }else if (topSettingName.equals(SETTING_VALIDATION)) { + castSettingToMap(settingName, settingValue, String.class, Object.class) + .forEach((name, value) -> { + if (name.equals( + SETTING_VALIDATION_PROGRAMLISTINGS_REQ_ROLE)) { + validationOps.setProgramlistingRequiresRole( + castSetting(settingName.subKey(name), value, Boolean.class)); + } else if (name.equals( + SETTING_VALIDATION_PROGRAMLISTINGS_REQ_LANG)) { + validationOps.setProgramlistingRequiresLanguage( + castSetting(settingName.subKey(name), value, Boolean.class)); + } else if (name.equals( + SETTING_VALIDATION_OUTPUT_FILES_CAN_USE_AUTOID) ) { - validationOps.setOutputFilesCanUseAutoID( - caseSettingToBoolean( - cfgFile, - settingName + "." + name, - ent.getValue())); - } else if (name.equals( - SETTING_VALIDATION_MAXIMUM_PROGRAMLISTING_WIDTH) + validationOps.setOutputFilesCanUseAutoID( + castSetting(settingName.subKey(name), value, Boolean.class)); + } else if (name.equals( + SETTING_VALIDATION_MAXIMUM_PROGRAMLISTING_WIDTH) ) { - validationOps.setMaximumProgramlistingWidth( - castSettingToInt( - cfgFile, - settingName + "." + name, - ent.getValue())); - } else { - throw newCfgFileException( - cfgFile, SETTING_VALIDATION, - "Unknown validation option: " + name); - } - } - } else if (settingName.equals(SETTING_OFFLINE)) { + validationOps.setMaximumProgramlistingWidth( + castSetting(settingName.subKey(name), value, Integer.class)); + } else { + throw newCfgFileException(settingName.subKey(name), "Unknown validation option: " + name); + } + }); + } else if (topSettingName.equals(SETTING_OFFLINE)) { if (offline == null) { // Ignore if the caller has already set this - offline = caseSettingToBoolean(cfgFile, settingName, settingValue); + offline = castSetting(settingName, settingValue, Boolean.class); } - } else if (settingName.equals(SETTING_SIMPLE_NAVIGATION_MODE)) { - simpleNavigationMode = caseSettingToBoolean(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_DEPLOY_URL)) { - deployUrl = castSettingToString(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_ONLINE_TRACKER_HTML)) { - onlineTrackerHTML = getFileContentForSetting(cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_SIMPLE_NAVIGATION_MODE)) { + simpleNavigationMode = castSetting(settingName, settingValue, Boolean.class); + } else if (topSettingName.equals(SETTING_DEPLOY_URL)) { + deployUrl = castSetting(settingName, settingValue, String.class); + } else if (topSettingName.equals(SETTING_ONLINE_TRACKER_HTML)) { + onlineTrackerHTML = getFileContentForSetting(settingName, settingValue); if (onlineTrackerHTML.startsWith("<!--")) { int commentEnd = onlineTrackerHTML.indexOf("-->"); if (commentEnd != -1) { @@ -715,66 +679,49 @@ public final class Transform { String eol = TextUtil.detectEOL(onlineTrackerHTML, "\n"); onlineTrackerHTML = onlineTrackerHTML.trim(); onlineTrackerHTML += eol; - } else if (settingName.equals(SETTING_COOKIE_CONSENT_SCRIPT_URL)) { - cookieConstentScriptURL = castSettingToString(cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_REMOVE_NODES_WHEN_ONLINE)) { - removeNodesWhenOnline = Collections.unmodifiableSet(new HashSet<String>( - castSettingToStringList(cfgFile, settingName, settingValue))); - } else if (settingName.equals(SETTING_ECLIPSE)) { - Map<String, Object> m = castSettingToMapWithStringKeys( - cfgFile, settingName, settingValue); - for (Entry<String, Object> ent : m.entrySet()) { - String name = ent.getKey(); - if (name.equals(SETTING_ECLIPSE_LINK_TO)) { - String value = castSettingToString( - cfgFile, - settingName + "." + name, - ent.getValue()); - eclipseLinkTo = value; - } else { - throw newCfgFileException( - cfgFile, settingName, - "Unknown Eclipse option: " + name); - } - } - } else if (settingName.equals(SETTING_LOCALE)) { - String s = castSettingToString( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_COOKIE_CONSENT_SCRIPT_URL)) { + cookieConstentScriptURL = castSetting(settingName, settingValue, String.class); + } else if (topSettingName.equals(SETTING_REMOVE_NODES_WHEN_ONLINE)) { + removeNodesWhenOnline = Collections.unmodifiableSet(new HashSet<>( + castSettingToList(settingName, settingValue, String.class))); + } else if (topSettingName.equals(SETTING_ECLIPSE)) { + castSettingToMap(settingName, settingValue, String.class, Object.class) + .forEach((name, value) -> { + if (name.equals(SETTING_ECLIPSE_LINK_TO)) { + eclipseLinkTo = castSetting( + settingName.subKey(name), value, String.class); + } else { + throw newCfgFileException(settingName, "Unknown Eclipse option: " + name); + } + }); + } else if (topSettingName.equals(SETTING_LOCALE)) { + String s = castSetting(settingName, settingValue, String.class); locale = StringUtil.deduceLocale(s); - } else if (settingName.equals(SETTING_TIME_ZONE)) { - String s = castSettingToString( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_TIME_ZONE)) { + String s = castSetting(settingName, settingValue, String.class); timeZone = TimeZone.getTimeZone(s); - } else if (settingName.equals(SETTING_GENERATE_ECLIPSE_TOC)) { - generateEclipseTOC = caseSettingToBoolean( - cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_SHOW_EDITORAL_NOTES)) { - showEditoralNotes = caseSettingToBoolean( - cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_SHOW_XXE_LOGO)) { - showXXELogo = caseSettingToBoolean( - cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_SEARCH_KEY)) { - searchKey = castSettingToString( - cfgFile, settingName, settingValue); - }else if (settingName.equals(SETTING_DISABLE_JAVASCRIPT)) { - disableJavaScript = caseSettingToBoolean( - cfgFile, settingName, settingValue); - } else if (settingName.equals(SETTING_CONTENT_DIRECTORY)) { - String s = castSettingToString( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_GENERATE_ECLIPSE_TOC)) { + generateEclipseTOC = castSetting(settingName, settingValue, Boolean.class); + } else if (topSettingName.equals(SETTING_SHOW_EDITORAL_NOTES)) { + showEditoralNotes = castSetting(settingName, settingValue, Boolean.class); + } else if (topSettingName.equals(SETTING_SHOW_XXE_LOGO)) { + showXXELogo = castSetting(settingName, settingValue, Boolean.class); + } else if (topSettingName.equals(SETTING_SEARCH_KEY)) { + searchKey = castSetting(settingName, settingValue, String.class); + }else if (topSettingName.equals(SETTING_DISABLE_JAVASCRIPT)) { + disableJavaScript = castSetting(settingName, settingValue, Boolean.class); + } else if (topSettingName.equals(SETTING_CONTENT_DIRECTORY)) { + String s = castSetting(settingName, settingValue, String.class); contentDir = new File(srcDir, s); if (!contentDir.isDirectory()) { - throw newCfgFileException(cfgFile, settingName, - "It's not an existing directory: " - + contentDir.getAbsolutePath()); + throw newCfgFileException( + settingName, + "It's not an existing directory: " + contentDir.getAbsolutePath()); } - } else if (settingName.equals(SETTING_LOWEST_FILE_ELEMENT_RANK) - || settingName.equals( - SETTING_LOWEST_PAGE_TOC_ELEMENT_RANK)) { + } else if (topSettingName.equals(SETTING_LOWEST_FILE_ELEMENT_RANK) + || topSettingName.equals(SETTING_LOWEST_PAGE_TOC_ELEMENT_RANK)) { DocumentStructureRank rank; - String strRank = castSettingToString( - cfgFile, settingName, settingValue); + String strRank = castSetting(settingName, settingValue, String.class); try { rank = DocumentStructureRank.valueOf( strRank.toUpperCase()); @@ -789,44 +736,30 @@ public final class Transform { } else { msg = "Unknown rank: " + strRank; } - throw newCfgFileException(cfgFile, settingName, - msg); + throw newCfgFileException(settingName, msg); } - if (settingName.equals( - SETTING_LOWEST_FILE_ELEMENT_RANK)) { + if (topSettingName.equals(SETTING_LOWEST_FILE_ELEMENT_RANK)) { lowestFileElemenRank = rank; - } else if (settingName.equals( - SETTING_LOWEST_PAGE_TOC_ELEMENT_RANK)) { + } else if (topSettingName.equals(SETTING_LOWEST_PAGE_TOC_ELEMENT_RANK)) { lowestPageTOCElemenRank = rank; } else { throw new BugException("Unexpected setting name."); } - } else if (settingName.equals(SETTING_MAX_TOF_DISPLAY_DEPTH)) { - maxTOFDisplayDepth = castSettingToInt( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_MAX_TOF_DISPLAY_DEPTH)) { + maxTOFDisplayDepth = castSetting(settingName, settingValue, Integer.class); if (maxTOFDisplayDepth < 1) { - throw newCfgFileException(cfgFile, settingName, - "Value must be at least 1."); + throw newCfgFileException(settingName, "Value must be at least 1."); } - } else if (settingName.equals( - SETTING_MAX_MAIN_TOF_DISPLAY_DEPTH)) { - maxMainTOFDisplayDepth = castSettingToInt( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_MAX_MAIN_TOF_DISPLAY_DEPTH)) { + maxMainTOFDisplayDepth = castSetting(settingName, settingValue, Integer.class); if (maxTOFDisplayDepth < 1) { - throw newCfgFileException(cfgFile, settingName, - "Value must be at least 1."); + throw newCfgFileException(settingName, "Value must be at least 1."); } - } else if (settingName.equals(SETTING_NUMBERED_SECTIONS)) { - numberedSections = caseSettingToBoolean( - cfgFile, settingName, settingValue); + } else if (topSettingName.equals(SETTING_NUMBERED_SECTIONS)) { + numberedSections = castSetting(settingName, settingValue, Boolean.class); } else { - throw newCfgFileException(cfgFile, "Unknown setting: \"" - + settingName - + "\". (Hint: See the list of available " - + "settings in the Java API documentation of " - + Transform.class.getName() + ". Also, note that " - + "setting names are case-sensitive.)"); + throw newCfgFileException(settingName, "Unknown setting name."); } } // for each cfg settings @@ -879,12 +812,11 @@ public final class Transform { // Initialize state fields - primaryIndexTermLookup = new HashMap<String, List<NodeModel>>(); - secondaryIndexTermLookup - = new HashMap<String, SortedMap<String, List<NodeModel>>>(); - elementsById = new HashMap<String, Element>(); - tocNodes = new ArrayList<TOCNode>(); - indexEntries = new ArrayList<String>(); + primaryIndexTermLookup = new HashMap<>(); + secondaryIndexTermLookup = new HashMap<>(); + elementsById = new HashMap<>(); + tocNodes = new ArrayList<>(); + indexEntries = new ArrayList<>(); // Setup FreeMarker: @@ -948,35 +880,27 @@ public final class Transform { tabEnt.setValue(resolveDocgenURL(SETTING_TABS, tabEnt.getValue())); } } - if (secondaryTabs != null) { - for (Map<String, String> tab : secondaryTabs.values()) { - tab.put("href", resolveDocgenURL(SETTING_SECONDARY_TABS, tab.get("href"))); - } + for (Map<String, String> secondaryTab : secondaryTabs.values()) { + secondaryTab.put("href", resolveDocgenURL(SETTING_SECONDARY_TABS, secondaryTab.get("href"))); } if (externalBookmarks != null) { for (Entry<String, String> bookmarkEnt : externalBookmarks.entrySet()) { bookmarkEnt.setValue(resolveDocgenURL(SETTING_EXTERNAL_BOOKMARKS, bookmarkEnt.getValue())); } } - if (socialLinks != null) { - for (Map<String, String> tab : socialLinks.values()) { - tab.put("href", resolveDocgenURL(SETTING_SOCIAL_LINKS, tab.get("href"))); - } + for (Map<String, String> tab : socialLinks.values()) { + tab.put("href", resolveDocgenURL(SETTING_SOCIAL_LINKS, tab.get("href"))); } - if (footerSiteMap != null) { - for (Map<String, String> links : footerSiteMap.values()) { - for (Map.Entry<String, String> link : links.entrySet()) { - link.setValue(resolveDocgenURL(SETTING_FOOTER_SITEMAP, link.getValue())); - } + for (Map<String, String> links : footerSiteMap.values()) { + for (Map.Entry<String, String> link : links.entrySet()) { + link.setValue(resolveDocgenURL(SETTING_FOOTER_SITEMAP, link.getValue())); } } if (logo != null) { resolveLogoHref(logo); } - if (sideTOCLogos != null) { - for (Logo logo : sideTOCLogos) { - resolveLogoHref(logo); - } + for (Logo logo : sideTOCLogos) { + resolveLogoHref(logo); } // - Create destination directory: @@ -990,10 +914,9 @@ public final class Transform { for (Entry<String, String> ent : internalBookmarks.entrySet()) { String id = ent.getValue(); if (!elementsById.containsKey(id)) { - throw newCfgFileException(cfgFile, - SETTING_INTERNAL_BOOKMARKS, - "No element with id \"" + id - + "\" exists in the book."); + throw newCfgFileException( + SettingName.topLevel(cfgFile, SETTING_INTERNAL_BOOKMARKS), + "No element with id \"" + id + "\" exists in the book."); } } @@ -1154,8 +1077,8 @@ public final class Transform { } } catch (freemarker.core.StopException e) { throw new DocgenException(e.getMessage()); - } catch (DocgenSubstitutionTemplateException e) { - throw new DocgenException("Docgen substitution in document text failed; see cause exception", e); + } catch (DocgenTagException e) { + throw new DocgenException("Docgen tag evaluation in document text failed; see cause exception", e); } catch (TemplateException e) { throw new BugException(e); } @@ -1338,286 +1261,37 @@ public final class Transform { } } - private DocgenException newCfgFileException( - File cfgFile, String settingName, String desc) { - settingName = settingName.replace(".", "\" per \""); - return newCfgFileException(cfgFile, "Wrong value for setting \"" - + settingName + "\": " + desc); + private static Logo castMapToLogo(SettingName settingName, Object settingValue) { + Map<String, String> logoMap = castSetting( + settingName, + settingValue, false, + Map.class, + new MapEntryType(String.class, SETTING_LOGO_MAP_KEYS, String.class)); + return new Logo( + logoMap.get(SETTING_LOGO_KEY_SRC), + logoMap.get(SETTING_LOGO_KEY_ALT), + logoMap.get(SETTING_LOGO_KEY_HREF)); } - private DocgenException newCfgFileException(File cfgFile, String desc) { - return newCfgFileException(cfgFile, desc, (Throwable) null); - } - - private DocgenException newCfgFileException(File cfgFile, String desc, - Throwable cause) { - StringBuilder sb = new StringBuilder(); - sb.append("Wrong configuration"); - if (cfgFile != null) { - sb.append(" file \""); - sb.append(cfgFile.getAbsolutePath()); - sb.append("\""); - } - sb.append(": "); - sb.append(desc); - return new DocgenException(sb.toString(), cause); - } - - @SuppressWarnings("unchecked") - private Map<String, Object> castSettingToMapWithStringKeys( - File cfgFile, String settingName, Object settingValue) - throws DocgenException { - if (!(settingValue instanceof Map)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a map (like { key1: value1, key2: value2 }), but " - + "it's a " + CJSONInterpreter.cjsonTypeOf(settingValue) - + "."); - } - for (Object key : ((Map<?, ?>) settingValue).keySet()) { - if (!(key instanceof String)) { - throw newCfgFileException( - cfgFile, settingName, - "All keys should be String-s, but one of them is a(n) " - + CJSONInterpreter.cjsonTypeOf(settingValue) + "."); - } - } - return (Map<String, Object>) settingValue; - } - - @SuppressWarnings("unchecked") - private List<String> castSettingToStringList( - File cfgFile, String settingName, Object settingValue) - throws DocgenException { - List<?> settingValueAsList = castSettingToList(cfgFile, settingName, settingValue); - for (int i = 0; i < settingValueAsList.size(); i++) { - Object listItem = settingValueAsList.get(i); - if (!(listItem instanceof String)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a list of String-s (like [\"value1\", \"value2\", ... \"valueN\"]), but at index " - + i +" (0-based) there's a " + CJSONInterpreter.cjsonTypeOf(listItem) - + "."); - } - } - return (List<String>) settingValue; - } - - @SuppressWarnings("unchecked") - private List<Map<String, Object>> castSettingToListOfMapsWithStringKeys( - File cfgFile, String settingName, Object settingValue) - throws DocgenException { - List<?> settingValueAsList = castSettingToList(cfgFile, settingName, settingValue); - for (int i = 0; i < settingValueAsList.size(); i++) { - castSettingToMapWithStringKeys(cfgFile, settingName + "[" + i + "]", settingValueAsList.get(i)); - } - return (List) settingValue; - } - - private List<?> castSettingToList(File cfgFile, String settingName, Object settingValue) throws DocgenException { - if (!(settingValue instanceof List)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a list (like [value1, value2, ... valueN]), but " - + "it's a " + CJSONInterpreter.cjsonTypeOf(settingValue) - + "."); - } - return (List<?>) settingValue; - } - - private String castSettingToString(File cfgFile, - String settingName, Object settingValue) throws DocgenException { - if (!(settingValue instanceof String)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a string, but it's a " - + CJSONInterpreter.cjsonTypeOf(settingValue) + "."); - } - return (String) settingValue; - } - - private boolean caseSettingToBoolean(File cfgFile, - String settingName, Object settingValue) throws DocgenException { - if (!(settingValue instanceof Boolean)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a boolean (i.e., true or false), but it's a " - + CJSONInterpreter.cjsonTypeOf(settingValue) + "."); - } - return (Boolean) settingValue; - } - - private int castSettingToInt(File cfgFile, - String settingName, Object settingValue) - throws DocgenException { - - if (!(settingValue instanceof Number)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be an number, but it's a " - + CJSONInterpreter.cjsonTypeOf(settingValue) + "."); - } - if (!(settingValue instanceof Integer)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be an integer number (32 bits max), but it's: " - + settingValue); - } - return ((Integer) settingValue).intValue(); - } - - /* Unused at the moment - @SuppressWarnings("unchecked") - private List<String> castSettingToListOfStrings(File cfgFile, - String settingName, Object settingValue) throws DocgenException { - if (!(settingValue instanceof List)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a list, but it's a " - + CJSONInterpreter.cjsonTypeOf(settingValue) + "."); - } - List ls = (List) settingValue; - - for (Object i : ls) { - if (!(i instanceof String)) { - throw newCfgFileException( - cfgFile, settingName, - "Should be a list of strings, but one if the list items " - + "is a " + CJSONInterpreter.cjsonTypeOf(i) + "."); - } - } - - return ls; - } - */ - - private String castSettingValueMapValueToString(File cfgFile, - String settingName, Object mapEntryValue) throws DocgenException { - if (mapEntryValue != null && !(mapEntryValue instanceof String)) { - throw newCfgFileException(cfgFile, settingName, - "The values in the key-value pairs of this map must be " - + "strings, but some of them is a " - + CJSONInterpreter.cjsonTypeOf(mapEntryValue) + "."); - } - return (String) mapEntryValue; - } - - @SuppressWarnings("unchecked") - private Map<String, String> castSettingValueMapValueToMapOfStringString(File cfgFile, - String settingName, Object mapEntryValue, Set<String> requiredKeys, Set<String> optionalKeys) - throws DocgenException { - if (!(mapEntryValue instanceof Map)) { - throw newCfgFileException(cfgFile, settingName, - "The values in the key-value pairs of this map must be " - + "Map-s, but some of them is a " - + CJSONInterpreter.cjsonTypeOf(mapEntryValue) + "."); - } - - if (requiredKeys == null) requiredKeys = Collections.emptySet(); - if (optionalKeys == null) optionalKeys = Collections.emptySet(); - - Map<?, ?> mapEntryValueAsMap = (Map<?, ?>) mapEntryValue; - for (Entry<?, ?> valueEnt : mapEntryValueAsMap.entrySet()) { - Object key = valueEnt.getKey(); - if (!(key instanceof String)) { - throw newCfgFileException(cfgFile, settingName, - "The values in the key-value pairs of this map must be " - + "Map<String, String>-s, but some of the keys is a " - + CJSONInterpreter.cjsonTypeOf(mapEntryValue) + "."); - } - if (!(valueEnt.getValue() instanceof String)) { - throw newCfgFileException(cfgFile, settingName, - "The values in the key-value pairs of this map must be " - + "Map<String, String>-s, but some of the values is a " - + CJSONInterpreter.cjsonTypeOf(valueEnt.getValue()) + "."); - } - if (!requiredKeys.contains(key) && !optionalKeys.contains(key)) { - StringBuilder sb = new StringBuilder(); - sb.append("Unsupported key: "); - sb.append(StringUtil.jQuote(key)); - sb.append(". Supported keys are: "); - boolean isFirst = true; - for (String supportedKey : requiredKeys) { - if (!isFirst) { - sb.append(", "); - } else { - isFirst = false; - } - sb.append(StringUtil.jQuote(supportedKey)); - } - for (String supportedKey : optionalKeys) { - if (!isFirst) { - sb.append(", "); - } else { - isFirst = false; - } - sb.append(StringUtil.jQuote(supportedKey)); - } - throw newCfgFileException(cfgFile, settingName, sb.toString()); - } - } - for (String requiredKey : requiredKeys) { - if (!mapEntryValueAsMap.containsKey(requiredKey)) { - throw newCfgFileException(cfgFile, settingName, - "Missing map key from nested Map: " + requiredKey); - } - } - return (Map<String, String>) mapEntryValue; - } - - private Logo castMapToLogo(File cfgFile, final String settingName, Map<String, Object> map) - throws DocgenException { - Logo logo = new Logo(); - for (Entry<String, Object> ent : map.entrySet()) { - String key = ent.getKey(); - String value = castSettingValueMapValueToString(cfgFile, settingName, ent.getValue()); - switch (key) { - case SETTING_LOGO_KEY_SRC: - logo.setSrc(value); - break; - case SETTING_LOGO_KEY_ALT: - logo.setAlt(value); - break; - case SETTING_LOGO_KEY_HREF: - logo.setHref(value); - break; - default: - throw newCfgFileException(cfgFile, SETTING_LOGO, "Unknown logo option: " + StringUtil.jQuote(key)); - } - } - - if (logo.getSrc() == null) { - throw newCfgFileException(cfgFile, SETTING_LOGO, "Missing logo option: " + SETTING_LOGO_KEY_SRC); - } - if (logo.getAlt() == null) { - throw newCfgFileException(cfgFile, SETTING_LOGO, "Missing logo option: " + SETTING_LOGO_KEY_ALT); - } - if (logo.getHref() == null) { - throw newCfgFileException(cfgFile, SETTING_LOGO, "Missing logo option: " + SETTING_LOGO_KEY_HREF); - } - - return logo; - } - - private String getFileContentForSetting(File cfgFile, - String settingName, Object settingValue) throws DocgenException { - String settingValueStr = castSettingToString(cfgFile, settingName, settingValue); + private String getFileContentForSetting(SettingName settingName, Object settingValue) { + String settingValueStr = castSetting(settingName, settingValue, String.class); File f = new File(getSourceDirectory(), settingValueStr); if (!f.exists()) { throw newCfgFileException( - cfgFile, settingName, + settingName, "File not found: " + f.toPath()); } try { return FileUtil.loadString(f, UTF_8); } catch (IOException e) { throw newCfgFileException( - cfgFile, "Error while reading file for setting \"" + settingName + "\": " + f.toPath(), + settingName, + "Error while reading file: " + f.toPath(), e); } } - private void copyCommonStatic(String staticFileName) throws IOException, DocgenException { + private void copyCommonStatic(String staticFileName) throws IOException { String resourcePath = "statics/" + staticFileName; try (InputStream in = Transform.class.getResourceAsStream(resourcePath)) { if (in == null) { @@ -1737,7 +1411,7 @@ public final class Transform { preprocessDOM_misc_inner(doc, new PreprocessDOMMisc_GlobalState(), new PreprocessDOMMisc_ParentSectState()); - indexEntries = new ArrayList<String>(primaryIndexTermLookup.keySet()); + indexEntries = new ArrayList<>(primaryIndexTermLookup.keySet()); Collections.sort(indexEntries, Collator.getInstance(locale)); } @@ -1898,10 +1572,10 @@ public final class Transform { } } - private void preprocessDOM_applyRemoveNodesWhenOnlineSetting(Document doc) throws DocgenException { + private void preprocessDOM_applyRemoveNodesWhenOnlineSetting(Document doc) { if (offline || removeNodesWhenOnline == null || removeNodesWhenOnline.isEmpty()) return; - HashSet<String> idsToRemoveLeft = new HashSet<String>(removeNodesWhenOnline); + HashSet<String> idsToRemoveLeft = new HashSet<>(removeNodesWhenOnline); preprocessDOM_applyRemoveNodesWhenOnlineSetting_inner( doc.getDocumentElement(), idsToRemoveLeft); if (!idsToRemoveLeft.isEmpty()) { @@ -1936,8 +1610,7 @@ public final class Transform { * Annotates the document structure nodes with so called ranks. * About ranks see: {@link #setting_lowestFileElementRank}. */ - private void preprocessDOM_addRanks(Document doc) - throws DocgenException { + private void preprocessDOM_addRanks(Document doc) { Element root = doc.getDocumentElement(); String rootName = root.getLocalName(); if (rootName.equals(E_BOOK)) { @@ -1954,9 +1627,7 @@ public final class Transform { } } - private void preprocessDOM_addRanks_underBookRank( - Element root) throws DocgenException { - + private void preprocessDOM_addRanks_underBookRank(Element root) { // Find the common rank: DocumentStructureRank commonRank = null; for (Element child : XMLUtil.childrenElementsOf(root)) { @@ -2003,8 +1674,7 @@ public final class Transform { } } - private void preprocessDOM_addRanks_underTruePart( - Node parent) throws DocgenException { + private void preprocessDOM_addRanks_underTruePart(Node parent) { for (Element child : XMLUtil.childrenElementsOf(parent)) { if (DOCUMENT_STRUCTURE_ELEMENTS.contains(child.getLocalName())) { child.setAttribute( @@ -2016,7 +1686,7 @@ public final class Transform { } private void preprocessDOM_addRanks_underChapterRankOrDeeper( - Element parent, int underSectionRank) throws DocgenException { + Element parent, int underSectionRank) { for (Element child : XMLUtil.childrenElementsOf(parent)) { if (DOCUMENT_STRUCTURE_ELEMENTS.contains(child.getLocalName())) { if (child.getLocalName().equals(E_SIMPLESECT)) { @@ -2047,7 +1717,7 @@ public final class Transform { } } - private void preprocessDOM_buildTOC(Document doc) throws DocgenException { + private void preprocessDOM_buildTOC(Document doc) { preprocessDOM_buildTOC_inner(doc, 0, null); if (tocNodes.size() > 0) { preprocessDOM_buildTOC_checkEnsureHasIndexHhml(tocNodes); @@ -2092,8 +1762,7 @@ public final class Transform { + "\" setting. Maybe it's incompatible with the structure of " + "this document.)"; - private void preprocessDOM_buildTOC_checkTOCTopology(TOCNode tocNode) - throws DocgenException { + private void preprocessDOM_buildTOC_checkTOCTopology(TOCNode tocNode) { // Check parent-child relation: TOCNode parent = tocNode.getParent(); if (parent != null && !parent.getElement().isSameNode( @@ -2190,8 +1859,7 @@ public final class Transform { + "\" setting. Maybe it's incompatible with the structure of " + "this document.)"; - private void preprocessDOM_buildTOC_checkFileTopology(TOCNode tocNode) - throws DocgenException { + private void preprocessDOM_buildTOC_checkFileTopology(TOCNode tocNode) { TOCNode firstChild = tocNode.getFirstChild(); if (firstChild != null) { boolean firstIsFileElement = firstChild.isFileElement(); @@ -2229,8 +1897,7 @@ public final class Transform { } private TOCNode preprocessDOM_buildTOC_inner(Node node, - final int sectionLevel, TOCNode parentTOCNode) - throws DocgenException { + final int sectionLevel, TOCNode parentTOCNode) { TOCNode curTOCNode = null; int newSectionLevel = sectionLevel; @@ -2316,7 +1983,7 @@ public final class Transform { return curTOCNode; } - private String getExternalLinkTOCNodeURLOrNull(Element elem) throws DocgenException { + private String getExternalLinkTOCNodeURLOrNull(Element elem) { if (elem.getParentNode() instanceof Document) { // The document element is never an external link ToC node. return null; @@ -2365,7 +2032,7 @@ public final class Transform { * @param tocNodes * @throws DocgenException */ - private void preprocessDOM_buildTOC_checkEnsureHasIndexHhml(List<TOCNode> tocNodes) throws DocgenException { + private void preprocessDOM_buildTOC_checkEnsureHasIndexHhml(List<TOCNode> tocNodes) { for (TOCNode tocNode : tocNodes) { if (tocNode.getOutputFileName() != null && tocNode.getOutputFileName().equals(FILE_INDEX_HTML)) { return; @@ -2468,13 +2135,13 @@ public final class Transform { String primaryText = primary.getFirstChild().getNodeValue().trim(); if (!primaryIndexTermLookup.containsKey(primaryText)) { - primaryIndexTermLookup.put(primaryText, new ArrayList<NodeModel>()); + primaryIndexTermLookup.put(primaryText, new ArrayList<>()); } if (secondary != null) { if (!secondaryIndexTermLookup.containsKey(primaryText)) { secondaryIndexTermLookup.put( - primaryText, new TreeMap<String, List<NodeModel>>()); + primaryText, new TreeMap<>()); } Map<String, List<NodeModel>> m = secondaryIndexTermLookup.get( primaryText); @@ -2482,7 +2149,7 @@ public final class Transform { .trim(); List<NodeModel> nodes = m.get(secondaryText); if (nodes == null) { - nodes = new ArrayList<NodeModel>(); + nodes = new ArrayList<>(); m.put(secondaryText, nodes); } nodes.add(NodeModel.wrap(node)); @@ -2670,8 +2337,7 @@ public final class Transform { return false; } - private String createElementLinkURL(final Element elem) - throws DocgenException { + private String createElementLinkURL(final Element elem) { if (elem.hasAttribute(A_DOCGEN_NOT_ADDRESSABLE)) { return null; } @@ -2747,6 +2413,7 @@ public final class Transform { private TemplateMethodModelEx createLinkFromID = new TemplateMethodModelEx() { + @Override public Object exec(@SuppressWarnings("rawtypes") final List args) throws TemplateModelException { if (args.size() != 1) { @@ -2765,7 +2432,7 @@ public final class Transform { }; - private String createLinkFromId(String id) throws DocgenException { + private String createLinkFromId(String id) { if (elementsById == null) { throw new IllegalStateException("Can't resolve ID as elementsById is still null: " + id); } @@ -2781,6 +2448,7 @@ public final class Transform { private TemplateMethodModelEx createLinkFromNode = new TemplateMethodModelEx() { + @Override public Object exec(@SuppressWarnings("rawtypes") final List args) throws TemplateModelException { @@ -2817,6 +2485,7 @@ public final class Transform { private TemplateMethodModelEx nodeFromID = new TemplateMethodModelEx() { + @Override public Object exec(@SuppressWarnings("rawtypes") List args) throws TemplateModelException { Node node = elementsById.get(getArgString(args, 0)); diff --git a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/footer.ftlh b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/footer.ftlh index 92ba2f0..94cf04e 100644 --- a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/footer.ftlh +++ b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/footer.ftlh @@ -25,15 +25,15 @@ <div class="site-footer"><#t> <#-- keep site-width inside so background extends --> <div class="site-width"><#t> - <#if footerSiteMap?? || socialLinks?? || showXXELogo> + <#if footerSiteMap?hasContent || socialLinks?hasContent || showXXELogo> <div class="footer-top"><#t> <div class="col-left sitemap"><#t> - <#if footerSiteMap??> + <#if footerSiteMap?hasContent> <@siteMap columns=footerSiteMap /><#t> </#if> </div><#t> <div class="col-right"><#t> - <#if socialLinks??> + <#if socialLinks?hasContent> <@social links=socialLinks /> </#if> <#if showXXELogo> diff --git a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/header.ftlh b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/header.ftlh index 1e8993b..ad5e96b 100644 --- a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/header.ftlh +++ b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/header.ftlh @@ -22,7 +22,7 @@ <#import "google.ftlh" as google> <#macro header> - <#if logo?? || tabs?? || secondaryTabs??> + <#if logo?? || tabs?hasContent || secondaryTabs?hasContent> <div class="header-top-bg"><#t> <div class="site-width header-top"><#t> <div id="hamburger-menu" role="button"></div><#t> @@ -34,7 +34,7 @@ </div> </#if> <@nav.tabs /><#t> - <#if secondaryTabs??> + <#if secondaryTabs?hasContent> <ul class="secondary-tabs"><#t> <#list secondaryTabs as tabTitle, tab> <li><#t> diff --git a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh index 601af62..3be163b 100644 --- a/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh +++ b/freemarker-docgen-core/src/main/resources/org/freemarker/docgen/core/templates/node-handlers.ftlh @@ -37,7 +37,7 @@ <#macro @element> <#stop "This DocBook element is not supported by the Docgen transformer, " - + "or wasn't expected where it occured: " + + "or wasn't expected where it occurred: " + .node?nodeName> </#macro> diff --git a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java similarity index 71% copy from freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java copy to freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java index 3ec8229..a0eab82 100644 --- a/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenException.java +++ b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java @@ -16,20 +16,17 @@ * specific language governing permissions and limitations * under the License. */ + package org.freemarker.docgen.core; -/** - * Exception that is docgen-specific. - */ -public class DocgenException extends Exception { - - public DocgenException(String message) { - super(message); - } - - public DocgenException( - String message, Throwable cause) { - super(message, cause); +import static org.junit.jupiter.api.Assertions.*; + +import org.junit.jupiter.api.Test; + +public class SettingNameTest { + @Test + public void toStringTest() { + assertEquals("a", SettingName.topLevel(null, "a").toString()); + assertEquals("a.b[1]", SettingName.topLevel(null, "a").subKey("b").subKey(1).toString()); } - -} \ No newline at end of file +} diff --git a/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java new file mode 100644 index 0000000..b615f3b --- /dev/null +++ b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingUtilsTest.java @@ -0,0 +1,152 @@ +/* + * 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.freemarker.docgen.core; + +import static org.freemarker.docgen.core.SettingUtils.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import java.util.Map; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +public class SettingUtilsTest { + + private static final SettingName SETTING_NAME = SettingName.topLevel(null, "foo"); + + @Test + public void testBasics() { + { + Object originalValue = 1; + Object value = castSetting(SettingName.topLevel(null, "a"), originalValue, Integer.class); + assertEquals(originalValue, value); + } + { + ImmutableList<ImmutableList<?>> originalValue = ImmutableList.of(ImmutableList.of(), ImmutableList.of(1)); + Object value = castSetting( + SETTING_NAME, + originalValue, true, + List.class, + new ListItemType(List.class), + new ListItemType(Integer.class) + ); + assertEquals(originalValue, value); + } + { + ImmutableMap<String, ImmutableMap<?, ?>> originalValue = ImmutableMap.of( + "x", ImmutableMap.of(), + "y", ImmutableMap.of("u", 1)); + Object value = castSetting( + SETTING_NAME, + originalValue, + Map.class, + new MapEntryType<>(String.class, Map.class), + new MapEntryType(String.class, Integer.class) + ); + assertEquals(originalValue, value); + } + } + + @Test + public void testOptional() { + assertNull(castSetting(SETTING_NAME, null, true, Integer.class)); + try { + castSetting(SETTING_NAME, null, Integer.class); + fail(); + } catch (DocgenException e) { + assertThat( + e.getMessage(), + allOf(containsString("required"), containsString(SETTING_NAME.toString()))); + } + } + + @Test + public void testMapKeyValidation() { + ImmutableSet<String> requiredKeys = ImmutableSet.of("reqKey1", "reqKey2"); + ImmutableSet<String> optionalKeys = ImmutableSet.of("optKey"); + { + ImmutableMap<String, Integer> originalValue = ImmutableMap.of( + "reqKey1", 1, + "reqKey2", 2, + "optKey", 3); + Object value = castSetting( + SETTING_NAME, originalValue, + Map.class, + new MapEntryType( + String.class, requiredKeys, optionalKeys, + Integer.class)); + assertEquals(originalValue, value); + } + { + ImmutableMap<String, Integer> originalValue = ImmutableMap.of( + "reqKey1", 1, + "reqKey2", 2); + Object value = castSetting( + SETTING_NAME, + originalValue, + Map.class, + new MapEntryType( + String.class, requiredKeys, optionalKeys, + Integer.class)); + assertEquals(originalValue, value); + } + try { + castSetting( + SETTING_NAME, + ImmutableMap.of( + "reqKey1", 1, + "optKey", 3), + Map.class, + new MapEntryType( + String.class, requiredKeys, optionalKeys, + Integer.class)); + fail(); + } catch (DocgenException e) { + assertThat( + e.getMessage(), + allOf(containsString("reqKey2"), containsString(SETTING_NAME.toString()))); + } + try { + castSetting( + SETTING_NAME, + ImmutableMap.of( + "reqKey1", 1, + "reqKey2", 2, + "wrongKey", 2), + Map.class, + new MapEntryType( + String.class, requiredKeys, optionalKeys, + Integer.class)); + fail(); + } catch (DocgenException e) { + assertThat( + e.getMessage(), + allOf(containsString("wrongKey"), containsString(SETTING_NAME.toString()))); + } + } + +}
