From 9e3f39be7ffcfee4b6ca80f9a210f13a848b01cf Mon Sep 17 00:00:00 2001 From: Konstantin Krivopustov Date: Tue, 20 Mar 2018 11:35:06 +0400 Subject: [PATCH] PL-10565 Default methods in config interfaces --- .../core/config/ConfigInterfaceTest.groovy | 62 ++++++++++++++++ .../spec/cuba/core/config/TestConfig.java | 41 +++++++++++ .../cuba/core/config/ConfigDefaultMethod.java | 73 +++++++++++++++++++ .../cuba/core/config/ConfigGetter.java | 3 +- .../cuba/core/config/ConfigHandler.java | 2 +- .../cuba/core/config/ConfigMethod.java | 3 +- .../cuba/core/config/ConfigMethods.java | 2 +- .../cuba/core/config/ConfigSetter.java | 3 +- 8 files changed, 184 insertions(+), 5 deletions(-) create mode 100644 modules/core/test/spec/cuba/core/config/ConfigInterfaceTest.groovy create mode 100644 modules/core/test/spec/cuba/core/config/TestConfig.java create mode 100644 modules/global/src/com/haulmont/cuba/core/config/ConfigDefaultMethod.java diff --git a/modules/core/test/spec/cuba/core/config/ConfigInterfaceTest.groovy b/modules/core/test/spec/cuba/core/config/ConfigInterfaceTest.groovy new file mode 100644 index 0000000000..d543709bc8 --- /dev/null +++ b/modules/core/test/spec/cuba/core/config/ConfigInterfaceTest.groovy @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2008-2018 Haulmont. + * + * Licensed 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 spec.cuba.core.config + +import com.haulmont.cuba.core.global.AppBeans +import com.haulmont.cuba.core.global.Configuration +import com.haulmont.cuba.testsupport.TestContainer +import org.junit.ClassRule +import spock.lang.Shared +import spock.lang.Specification + +class ConfigInterfaceTest extends Specification { + + @Shared @ClassRule + public TestContainer cont = TestContainer.Common.INSTANCE + + private Configuration configuration + + void setup() { + configuration = AppBeans.get(Configuration) + } + + def "default method in configuration interface"() { + + def config = configuration.getConfig(TestConfig) + + when: "no value provided for 'foo' property" + + def foo = config.getFooOrDefault() + + then: "default method returns value of 'bar' property" + + foo == 'bar-value' + + when: "after setting own value" + + config.setFoo('foo-value') + foo = config.getFooOrDefault() + + then: "the own value is returned" + + foo == 'foo-value' + + cleanup: + + config.setFoo(null) + } +} diff --git a/modules/core/test/spec/cuba/core/config/TestConfig.java b/modules/core/test/spec/cuba/core/config/TestConfig.java new file mode 100644 index 0000000000..3cc2daa783 --- /dev/null +++ b/modules/core/test/spec/cuba/core/config/TestConfig.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2008-2018 Haulmont. + * + * Licensed 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 spec.cuba.core.config; + +import com.haulmont.cuba.core.config.Config; +import com.haulmont.cuba.core.config.Property; +import com.haulmont.cuba.core.config.Source; +import com.haulmont.cuba.core.config.SourceType; +import com.haulmont.cuba.core.config.defaults.Default; + +public interface TestConfig extends Config { + + @Property("test.bar") + @Source(type = SourceType.APP) + @Default("bar-value") + String getBar(); + + @Property("test.foo") + @Source(type = SourceType.DATABASE) + String getFoo(); + void setFoo(String value); + + default String getFooOrDefault() { + String foo = getFoo(); + return foo != null ? foo : getBar(); + } +} diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigDefaultMethod.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigDefaultMethod.java new file mode 100644 index 0000000000..aad06787f1 --- /dev/null +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigDefaultMethod.java @@ -0,0 +1,73 @@ +/* + * Copyright (c) 2008-2018 Haulmont. + * + * Licensed 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 com.haulmont.cuba.core.config; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Constructor; +import java.lang.reflect.Method; + +/** + * Invokes default method of configuration interfaces. + */ +public class ConfigDefaultMethod extends ConfigMethod { + + private final Class configInterface; + private final Method configMethod; + + public ConfigDefaultMethod(Class configInterface, Method configMethod) { + this.configInterface = configInterface; + this.configMethod = configMethod; + } + + @Override + public Object invoke(ConfigHandler handler, Object[] args, Object proxy) { + try { + // hack to invoke default method of an interface reflectively + Constructor lookupConstructor = + MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, Integer.TYPE); + if (!lookupConstructor.isAccessible()) { + lookupConstructor.setAccessible(true); + } + return lookupConstructor.newInstance(configInterface, MethodHandles.Lookup.PRIVATE) + .unreflectSpecial(configMethod, configInterface) + .bindTo(proxy) + .invokeWithArguments(args); + } catch (Throwable throwable) { + throw new RuntimeException("Error invoking default method of config interface", throwable); + } + } + + /** + * The ConfigDefaultMethod factory. + */ + public static final Factory FACTORY = new Factory() { + + /** + * The method is default and has a non-void return type. + */ + @Override + public boolean canHandle(Method method) { + return method.isDefault() && !Void.TYPE.equals(method.getReturnType()); + } + + @Override + public ConfigMethod newInstance(Class configInterface, Method configMethod) { + return new ConfigDefaultMethod(configInterface, configMethod); + } + }; + +} diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigGetter.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigGetter.java index a071095ccd..58170a2426 100644 --- a/modules/global/src/com/haulmont/cuba/core/config/ConfigGetter.java +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigGetter.java @@ -60,7 +60,7 @@ public class ConfigGetter extends ConfigAccessorMethod { * run-time default value was specified. */ @Override - public Object invoke(ConfigHandler handler, Object[] args) { + public Object invoke(ConfigHandler handler, Object[] args, Object proxy) { ConfigPersister configuration = handler.getPersister(); String str; if ((args == null) || (args.length == 0)) { @@ -134,6 +134,7 @@ public class ConfigGetter extends ConfigAccessorMethod { Class returnType = method.getReturnType(); Class[] parameterTypes = method.getParameterTypes(); return ConfigUtil.GET_RE.matcher(methodName).matches() && + !method.isDefault() && !Void.TYPE.equals(returnType) && (Boolean.TYPE.equals(returnType) || methodName.startsWith("get")) && ((parameterTypes.length == 0) || diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigHandler.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigHandler.java index e7fd30b771..995885150b 100644 --- a/modules/global/src/com/haulmont/cuba/core/config/ConfigHandler.java +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigHandler.java @@ -76,6 +76,6 @@ public class ConfigHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { ConfigMethod configMethod = ConfigMethods.getInstance(configInterface, method); - return configMethod.invoke(this, args); + return configMethod.invoke(this, args, proxy); } } \ No newline at end of file diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigMethod.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigMethod.java index 4ddb481558..84ba24e44c 100644 --- a/modules/global/src/com/haulmont/cuba/core/config/ConfigMethod.java +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigMethod.java @@ -36,9 +36,10 @@ public abstract class ConfigMethod { * * @param handler The handler. * @param args The method arguments. + * @param proxy The dynamic proxy created for the configuration interface. * @return The method result. */ - public abstract Object invoke(ConfigHandler handler, Object[] args); + public abstract Object invoke(ConfigHandler handler, Object[] args, Object proxy); /** * Interface describing a configuration method factory. diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigMethods.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigMethods.java index feb8acdd38..f87d6b9b62 100644 --- a/modules/global/src/com/haulmont/cuba/core/config/ConfigMethods.java +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigMethods.java @@ -45,7 +45,7 @@ public class ConfigMethods { * Supported configuration method factories. */ private static final ConfigMethod.Factory[] CONFIG_METHOD_FACTORIES = { - ConfigGetter.FACTORY, ConfigSetter.FACTORY + ConfigGetter.FACTORY, ConfigSetter.FACTORY, ConfigDefaultMethod.FACTORY }; /** diff --git a/modules/global/src/com/haulmont/cuba/core/config/ConfigSetter.java b/modules/global/src/com/haulmont/cuba/core/config/ConfigSetter.java index 0496116e95..dcc2d56bb2 100644 --- a/modules/global/src/com/haulmont/cuba/core/config/ConfigSetter.java +++ b/modules/global/src/com/haulmont/cuba/core/config/ConfigSetter.java @@ -55,7 +55,7 @@ public class ConfigSetter extends ConfigAccessorMethod { * filed value. */ @Override - public Object invoke(ConfigHandler handler, Object[] args) { + public Object invoke(ConfigHandler handler, Object[] args, Object proxy) { setProperty(handler.getPersister(), args[0]); return null; } @@ -90,6 +90,7 @@ public class ConfigSetter extends ConfigAccessorMethod { Class returnType = method.getReturnType(); Class[] parameterTypes = method.getParameterTypes(); return ConfigUtil.SET_RE.matcher(methodName).matches() && + !method.isDefault() && Void.TYPE.equals(returnType) && (parameterTypes.length == 1); }