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