From 222490a7a36b10c74d9c056a5a95ac0aa6f4e818 Mon Sep 17 00:00:00 2001
From: lmihalkovic <laurent.michalkovic@frentix.com>
Date: Fri, 13 May 2016 11:31:37 +0200
Subject: [PATCH] OO-2007: extends ModuleConfiguration with fragments (subset
 with shared key prefix) and strongly typed API

---
 .../olat/modules/IModuleConfiguration.java    | 139 +++++++++++++
 .../java/org/olat/modules/ModuleProperty.java | 158 +++++++++++++++
 .../modules/ModuleconfigurationFragment.java  | 190 ++++++++++++++++++
 3 files changed, 487 insertions(+)
 create mode 100644 src/main/java/org/olat/modules/IModuleConfiguration.java
 create mode 100644 src/main/java/org/olat/modules/ModuleProperty.java
 create mode 100644 src/main/java/org/olat/modules/ModuleconfigurationFragment.java

diff --git a/src/main/java/org/olat/modules/IModuleConfiguration.java b/src/main/java/org/olat/modules/IModuleConfiguration.java
new file mode 100644
index 00000000000..494d12ec3d3
--- /dev/null
+++ b/src/main/java/org/olat/modules/IModuleConfiguration.java
@@ -0,0 +1,139 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules;
+
+import org.olat.modules.ModuleProperty.ModulePropertyValue;
+
+/**
+ * A simple interface for dealing with sets of key/value pairs supporting the following
+ * <ul>
+ * <li>subsets based on a common key prefix (eg:  sendToUsers, sendToOwners --> prefix: sendTo)
+ * <li>strongly typed properties (the definition of "keyName" is associated with a java type)
+ * </ul>
+ * 
+ * <p>Initial date: May 6, 2016
+ * @author lmihalkovic, http://www.frentix.com
+ */
+public interface IModuleConfiguration {
+
+	/**
+	 * Factory method for creating a {@link IModuleConfiguration} instance with a given 
+	 * property name prefix, backed by a given instance of {@link ModuleConfiguration}.
+	 * This method allows a custom prefix/name separator to be specified. Passing {@code null}
+	 * as a separator is equivalent to passing an empty string {@code ""}.
+	 * 
+	 * @param fragmentName
+	 * @param sep
+	 * @param config
+	 * @return
+	 */
+	public static IModuleConfiguration fragment(String fragmentName, String sep, ModuleConfiguration config) {
+		return new ModuleconfigurationFragment(fragmentName, sep, config);
+	}
+
+	/**
+	 * Factory method for creating a {@link IModuleConfiguration} instance with a given 
+	 * property name prefix, backed by a given instance of {@link ModuleConfiguration}.
+	 * By default the property name will follow the pattern: prefix_XXXXX, where XXXXX
+	 * is the name passed as a parameter to the methods in this interface 
+	 * 
+	 * 
+	 * @param fragmentName
+	 * @param config
+	 * @return
+	 */
+	public static IModuleConfiguration fragment(String fragmentName, ModuleConfiguration config) {
+		return new ModuleconfigurationFragment(fragmentName, "_", config);
+	}
+
+	// ------------------------------------------------------------------------
+	
+	public default boolean has(String configKey) {
+		return get(configKey) != null;
+	}
+
+	public default boolean hasAnyOf(String...configKeys) {
+		for(String key : configKeys) {
+			if (get(key) != null) return true;
+		}
+		return false;
+	}
+
+	public default boolean allTrue(String... configKeys) {
+		boolean rc = false;
+		for(String key : configKeys) {
+			rc = rc & getBooleanSafe(key);
+			if (!rc) break;
+		}
+		return rc;
+	}
+
+	public default boolean anyTrue(String... configKeys) {
+		boolean rc = false;
+		for(String key : configKeys) {
+			rc = getBooleanSafe(key);
+			if (rc) break;
+		}
+		return rc;
+	}
+	
+
+	public boolean getBooleanSafe(String configKey);
+	public void setBooleanEntry(String configKey, boolean value);
+
+	public void set(String configKey, Object value);
+	public Object get(String configKey);
+	@SuppressWarnings("unchecked")
+	default public <T> T getAs(String configKey) {
+		Object val = get(configKey);
+		return val != null ? (T)val : null;
+	}
+
+	
+	// ------------------------------------------------------------------------
+	// Strongly typed API
+	
+	public <X> ModulePropertyValue<X> get(ModuleProperty<X> key);
+	public default <X> X val(ModuleProperty<X> key) {
+		return get(key).val();
+	}
+	public <X> void set(ModulePropertyValue<X> value);
+	public <X> void set(ModuleProperty<X> key, X value);
+
+	public default boolean has(ModuleProperty<?> key) {
+		ModulePropertyValue<?> val = get(key);
+		return val.isSet();
+	}
+
+	public boolean hasAnyOf(ModuleProperty<?>...keys);
+
+	public boolean anyTrue(ModuleProperty<Boolean> key);
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2);
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3);
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4);
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4, ModuleProperty<Boolean> key5);
+	
+	public boolean allTrue(ModuleProperty<Boolean> key);
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2);
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3);
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4);
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4, ModuleProperty<Boolean> key5);
+
+}
diff --git a/src/main/java/org/olat/modules/ModuleProperty.java b/src/main/java/org/olat/modules/ModuleProperty.java
new file mode 100644
index 00000000000..6c66c2968fd
--- /dev/null
+++ b/src/main/java/org/olat/modules/ModuleProperty.java
@@ -0,0 +1,158 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules;
+
+import java.lang.reflect.Array;
+import java.lang.reflect.GenericArrayType;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.lang.reflect.TypeVariable;
+import java.lang.reflect.WildcardType;
+
+/**
+ * A simple implementation of a strongly typed named property
+ * 
+ * <p>Initial date: May 6, 2016
+ * @author lmihalkovic, http://www.frentix.com
+ */
+public abstract class ModuleProperty<T> {
+
+	/**
+	 * A strongly typed property value
+	 * 
+	 * Initial date: May 6, 2016<br>
+	 * @author lmihalkovic, http://www.frentix.com
+	 *
+	 */
+	public static final class ModulePropertyValue<X> {
+		private X value;
+		private final ModuleProperty<X> def;
+		protected ModulePropertyValue(X value, ModuleProperty<X> def) {
+			this.value = value;
+			this.def = def;
+		}
+		public X val() {
+			if(!isSet()) return getDefault();
+			return value;
+		}
+		public void val(X val) {
+			this.value = val;
+		}
+		public X getDefault() {
+			return def.getDefault();
+		}
+		public boolean isSet() {
+			return this.value != null;
+		}
+		public String name() {
+			return def.name;
+		}
+	}
+	
+	private final String name;
+	private final T defaultValue;
+	private final Type type;
+	
+	public ModuleProperty(String name) {
+		this(name, null);
+	}
+	
+	public ModuleProperty(String name, T defaultValue) {
+		this.name = name;
+		this.defaultValue = defaultValue;
+		this.type = getType();
+	}
+	
+	public ModulePropertyValue<T> val(T value) {
+		return new ModulePropertyValue<T>(value, this);
+	}
+	
+	public String name() {
+		return this.name;
+	}
+	
+	public boolean hasDefault() {
+		return defaultValue != null;
+	}
+	
+	T getDefault() {
+		return this.defaultValue;
+	}
+	
+	@Override
+	public String toString() {
+		StringBuilder sb = new StringBuilder();
+		sb.append("[")
+			.append(name())
+			.append(":")
+			.append(type);
+		if (defaultValue!= null) {
+			sb.append(" {").append(defaultValue).append("}");					
+		}
+		sb.append("]");
+		return sb.toString();
+	}
+
+	// ------------------------------
+	// Internal
+	
+	private Type getType() {
+        Type superclass = getClass().getGenericSuperclass();
+        if (superclass instanceof Class) {
+            throw new RuntimeException("Missing type parameter.");
+        }
+        return ((ParameterizedType) superclass).getActualTypeArguments()[0];		
+	}
+	
+	@SuppressWarnings("unchecked")
+	Class<T> rawType() {
+		// this is ok or leads to a CCE later if the types do not match
+		return (Class<T>) getRawType(type);
+	}
+	
+	private static Class<?> getRawType(Type type) {
+		if (type instanceof Class<?>) {
+			// type is a class
+			return (Class<?>) type;
+		} else if (type instanceof ParameterizedType) {
+			ParameterizedType parameterizedType = (ParameterizedType) type;
+			Type rawType = parameterizedType.getRawType();
+			checkArgument(rawType instanceof Class);
+			return (Class<?>) rawType;
+		} else if (type instanceof GenericArrayType) {
+			Type componentType = ((GenericArrayType) type).getGenericComponentType();
+			return Array.newInstance(getRawType(componentType), 0).getClass();
+		} else if (type instanceof TypeVariable) {
+			return Object.class;
+		} else if (type instanceof WildcardType) {
+			return getRawType(((WildcardType) type).getUpperBounds()[0]);
+		} else {
+			String className = type == null ? "null" : type.getClass().getName();
+			throw new IllegalArgumentException("Expected a Class, ParameterizedType, or " + "GenericArrayType, but <"
+					+ type + "> is of type " + className);
+		}
+	}
+
+	private static void checkArgument(boolean condition) {
+		if (!condition) {
+			throw new IllegalArgumentException();
+		}
+	}
+}
diff --git a/src/main/java/org/olat/modules/ModuleconfigurationFragment.java b/src/main/java/org/olat/modules/ModuleconfigurationFragment.java
new file mode 100644
index 00000000000..3a13748179f
--- /dev/null
+++ b/src/main/java/org/olat/modules/ModuleconfigurationFragment.java
@@ -0,0 +1,190 @@
+/**
+ * <a href="http://www.openolat.org">
+ * OpenOLAT - Online Learning and Training</a><br>
+ * <p>
+ * Licensed under the Apache License, Version 2.0 (the "License"); <br>
+ * you may not use this file except in compliance with the License.<br>
+ * You may obtain a copy of the License at the
+ * <a href="http://www.apache.org/licenses/LICENSE-2.0">Apache homepage</a>
+ * <p>
+ * Unless required by applicable law or agreed to in writing,<br>
+ * software distributed under the License is distributed on an "AS IS" BASIS, <br>
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <br>
+ * See the License for the specific language governing permissions and <br>
+ * limitations under the License.
+ * <p>
+ * Initial code contributed and copyrighted by<br>
+ * frentix GmbH, http://www.frentix.com
+ * <p>
+ */
+package org.olat.modules;
+
+import java.util.Date;
+import java.util.List;
+
+import org.olat.modules.ModuleProperty.ModulePropertyValue;
+
+/**
+ * A sub-section of a standard module configuration. This is particularly 
+ * useful in case a block of key/value pairs would be repeated, with a
+ * different prefix.
+ * 
+ * <p>Initial date: May 6, 2016
+ * @author lmihalkovic, http://www.frentix.com
+ */
+public class ModuleconfigurationFragment implements IModuleConfiguration {
+
+	private final ModuleConfiguration config;
+	private final String fragmentName;
+	private final String sep; 
+	
+	protected ModuleconfigurationFragment(String fragmentName, String separator, ModuleConfiguration config) {
+		this.config = config;
+		this.fragmentName = fragmentName;
+		this.sep = separator == null ? "" : separator;
+	}
+	
+	protected final String key(String configKey) {
+		return fragmentName + sep + configKey;
+	}
+
+	@Override
+	public boolean getBooleanSafe(String configKey) {
+		return config.getBooleanSafe(key(configKey));
+	}
+
+	@Override
+	public void setBooleanEntry(String configKey, boolean value) {
+		config.setBooleanEntry(key(configKey), value);
+	}
+
+	@Override
+	public void set(String configKey, Object value) {
+		config.set(key(configKey), value);
+	}
+
+	@Override
+	public Object get(String configKey) {
+		return config.get(key(configKey));
+	}
+
+	public <U> List<U> getList(String configKey, Class<U> cl) {
+		return config.getList(key(configKey), cl);
+	}
+
+	@Override
+	public boolean anyTrue(ModuleProperty<Boolean> key) {
+		return _anyTrue(key);
+	}
+	@Override
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2) {
+		return _anyTrue(key1, key2);
+	}
+	@Override
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3) {
+		return _anyTrue(key1, key2, key3);
+	}
+	@Override
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4) {
+		return _anyTrue(key1, key2, key3, key4);
+	}
+	@Override
+	public boolean anyTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4, ModuleProperty<Boolean> key5) {
+		return _anyTrue(key1, key2, key3, key4, key5);
+	}
+	@SafeVarargs
+	protected final boolean _anyTrue(ModuleProperty<Boolean>... keys) {
+		boolean rc = false;
+		for(ModuleProperty<Boolean> key : keys) {
+			rc = getBooleanSafe(key.name());
+			if (rc) return true;
+		}
+		return rc;
+	}
+	
+	
+	@Override
+	public boolean allTrue(ModuleProperty<Boolean> key) {
+		return _allTrue(key);
+	}
+	@Override
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2) {
+		return _allTrue(key1, key2);
+	}
+	@Override
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3) {
+		return _allTrue(key1, key2, key3);
+	}
+	@Override
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4) {
+		return _allTrue(key1, key2, key3, key3);
+	}
+	@Override
+	public boolean allTrue(ModuleProperty<Boolean> key1, ModuleProperty<Boolean> key2, ModuleProperty<Boolean> key3, ModuleProperty<Boolean> key4, ModuleProperty<Boolean> key5) {
+		return _allTrue(key1, key2, key3, key4, key5);
+	}
+	
+	@SafeVarargs
+	protected final boolean _allTrue(ModuleProperty<Boolean>... keys) {
+		for(ModuleProperty<Boolean> key : keys) {
+			boolean rc = getBooleanSafe(key.name());
+			if (!rc) return false;
+		}
+		return true;
+	}
+	
+	@Override
+	public <X> ModulePropertyValue<X> get(ModuleProperty<X> key) {
+		ModulePropertyValue<X> value = valueOf(key);
+		return value;
+	}
+	
+	@Override
+	public <X> void set(ModulePropertyValue<X> val) {
+		X value = val.val();
+		config.set(key(val.name()), value);
+	}
+
+	@Override
+	public <X> void set(ModuleProperty<X> key, X value) {
+		config.set(key(key.name()), value);
+	}
+	
+	@Override
+	public boolean hasAnyOf(ModuleProperty<?>... keys) {
+		for(ModuleProperty<?> key : keys) {
+			ModulePropertyValue<?> val = get(key);
+			if (val.isSet()) return true;
+		}
+		return false;
+	}
+	
+	protected <X> ModulePropertyValue<X> valueOf(ModuleProperty<X> key) {
+		Class<X> klass = key.rawType();
+		X val = null;
+		String name = key.name();
+		if(klass == Boolean.class) {			
+			Boolean b = (key.hasDefault() ? config.getBooleanSafe(key(name), (boolean)(key.getDefault())) : config.getBooleanEntry(key(name)));
+			val = klass.cast(b);
+		} else if (klass == Float.class) {
+			Float f = config.getFloatEntry(key(name));
+			val = klass.cast(f);
+		} else if (klass == Integer.class) {
+			if(!key.hasDefault()) {
+				throw new IllegalArgumentException("Integer keys MUST define a default value");
+			}
+			Integer i = config.getIntegerSafe(key(name), (int)key.getDefault());
+			val = klass.cast(i);
+		} else if (klass == Date.class) {
+			Date d = config.getDateValue(key(name));
+			val = klass.cast(d);
+		} else {
+			// This is no different than the normal CCE that would happen
+			// in the calling code when doing
+			//    SomeType val = (SomeType) config.get("keyName");
+			val = klass.cast(get(name));
+		}		
+		return new ModulePropertyValue<X>(val, key);
+	}
+
+}
-- 
GitLab