Reworked setting validation API-s, and some other cleanup of old code.
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 @@
/**
* 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 @@
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 @@
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 @@
} 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 @@
/**
* 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/DocgenTagException.java b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenTagException.java
new file mode 100644
index 0000000..6d7ed81
--- /dev/null
+++ b/freemarker-docgen-core/src/main/java/org/freemarker/docgen/core/DocgenTagException.java
@@ -0,0 +1,37 @@
+/*
+ * 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 freemarker.core.Environment;
+import freemarker.template.TemplateException;
+
+/**
+ * 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 DocgenTagException extends TemplateException {
+ public DocgenTagException(String description, Environment env) {
+ super(description, env);
+ }
+
+ public DocgenTagException(String description, Throwable cause, Environment env) {
+ super(description, cause, env);
+ }
+}
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 @@
/** 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.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.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.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 @@
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 @@
/** 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 @@
*/
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 @@
// -------------------------------------------------------------------------
// Settings:
+ private File cfgFile;
+
private File destDir;
private File srcDir;
@@ -370,11 +388,9 @@
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 @@
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 @@
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 @@
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 @@
// 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 @@
}
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 @@
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 @@
} 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 @@
// 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 @@
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 @@
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 @@
}
} 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 @@
}
}
- 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 @@
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 @@
}
}
- 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 @@
* 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 @@
}
}
- 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 @@
}
}
- 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 @@
}
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 @@
}
}
- 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 @@
+ "\" 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 @@
+ "\" 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 @@
}
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 @@
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 @@
* @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 @@
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 @@
.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 @@
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 @@
private TemplateMethodModelEx createLinkFromID = new TemplateMethodModelEx() {
+ @Override
public Object exec(@SuppressWarnings("rawtypes") final List args)
throws TemplateModelException {
if (args.size() != 1) {
@@ -2765,7 +2432,7 @@
};
- 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 @@
private TemplateMethodModelEx createLinkFromNode
= new TemplateMethodModelEx() {
+ @Override
public Object exec(@SuppressWarnings("rawtypes") final List args)
throws TemplateModelException {
@@ -2817,6 +2485,7 @@
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/test/java/org/freemarker/docgen/core/SettingNameTest.java b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
new file mode 100644
index 0000000..a0eab82
--- /dev/null
+++ b/freemarker-docgen-core/src/test/java/org/freemarker/docgen/core/SettingNameTest.java
@@ -0,0 +1,32 @@
+/*
+ * 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.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());
+ }
+}
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())));
+ }
+ }
+
+}