| /* |
| * 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.apache.felix.dm.lambda.impl; |
| |
| import java.util.ArrayList; |
| import java.util.Dictionary; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Objects; |
| import java.util.function.Function; |
| import java.util.stream.Stream; |
| |
| import org.apache.felix.dm.BundleDependency; |
| import org.apache.felix.dm.Component; |
| import org.apache.felix.dm.DependencyManager; |
| import org.apache.felix.dm.lambda.BundleDependencyBuilder; |
| import org.apache.felix.dm.lambda.callbacks.CbBundle; |
| import org.apache.felix.dm.lambda.callbacks.CbBundleComponent; |
| import org.apache.felix.dm.lambda.callbacks.InstanceCbBundle; |
| import org.apache.felix.dm.lambda.callbacks.InstanceCbBundleComponent; |
| import org.osgi.framework.Bundle; |
| |
| @SuppressWarnings("unchecked") |
| public class BundleDependencyBuilderImpl implements BundleDependencyBuilder { |
| private String m_added; |
| private String m_changed; |
| private String m_removed; |
| private Object m_instance; |
| private boolean m_autoConfig = true; |
| private boolean m_autoConfigInvoked = false; |
| private boolean m_required = true; |
| private Bundle m_bundle; |
| private String m_filter; |
| private int m_stateMask = -1; |
| private boolean m_propagate; |
| private Object m_propagateInstance; |
| private String m_propagateMethod; |
| private Function<Bundle, Dictionary<?, ?>> m_propagateCallback; |
| private final Component m_component; |
| |
| enum Cb { |
| ADD, |
| CHG, |
| REM |
| }; |
| |
| private final Map<Cb, List<MethodRef<Object>>> m_refs = new HashMap<>(); |
| |
| @FunctionalInterface |
| interface MethodRef<I> { |
| public void accept(I instance, Component c, Bundle bundle); |
| } |
| |
| /** |
| * Class used to call a supplier that returns Propagated properties |
| */ |
| private class Propagate { |
| @SuppressWarnings("unused") |
| Dictionary<?, ?> propagate(Bundle bundle) { |
| return m_propagateCallback.apply(bundle); |
| } |
| } |
| |
| public BundleDependencyBuilderImpl (Component component) { |
| m_component = component; |
| } |
| |
| @Override |
| public BundleDependencyBuilder autoConfig(boolean autoConfig) { |
| m_autoConfig = autoConfig; |
| m_autoConfigInvoked = true; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder autoConfig() { |
| autoConfig(true); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder required(boolean required) { |
| m_required = required; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder required() { |
| required(true); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder bundle(Bundle bundle) { |
| m_bundle = bundle; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder filter(String filter) throws IllegalArgumentException { |
| m_filter = filter; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder mask(int mask) { |
| m_stateMask = mask; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder propagate(boolean propagate) { |
| m_propagate = propagate; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder propagate() { |
| propagate(true); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder propagate(Object instance, String method) { |
| if (m_propagateCallback != null || m_propagate) throw new IllegalStateException("Propagate callback already set."); |
| Objects.nonNull(method); |
| Objects.nonNull(instance); |
| m_propagateInstance = instance; |
| m_propagateMethod = method; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder propagate(Function<Bundle, Dictionary<?, ?>> instance) { |
| if (m_propagateInstance != null || m_propagate) throw new IllegalStateException("Propagate callback already set."); |
| m_propagateCallback = instance; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder callbackInstance(Object callbackInstance) { |
| m_instance = callbackInstance; |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder add(String add) { |
| callbacks(add, null, null); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder change(String change) { |
| callbacks(null, change, null); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder remove(String remove) { |
| callbacks(null, null, remove); |
| return this; |
| } |
| |
| private BundleDependencyBuilder callbacks(String added, String changed, String removed) { |
| requiresNoMethodRefs(); |
| m_added = added != null ? added : m_added; |
| m_changed = changed != null ? changed : m_changed; |
| m_removed = removed != null ? removed : m_removed; |
| if (! m_autoConfigInvoked) m_autoConfig = false; |
| return this; |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder add(CbBundle<T> add) { |
| return callbacks(add, null, null); |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder change(CbBundle<T> change) { |
| return callbacks(null, change, null); |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder remove(CbBundle<T> remove) { |
| return callbacks(null, null, remove); |
| } |
| |
| private <T> BundleDependencyBuilder callbacks(CbBundle<T> add, CbBundle<T> change, CbBundle<T> remove) { |
| if (add != null) { |
| setComponentCallbackRef(Cb.ADD, Helpers.getLambdaArgType(add, 0), (inst, component, bundle) -> add.accept ((T) inst, bundle)); |
| } |
| if (change != null) { |
| setComponentCallbackRef(Cb.CHG, Helpers.getLambdaArgType(change, 0), (inst, component, bundle) -> change.accept ((T) inst, bundle)); |
| } |
| if (remove != null) { |
| setComponentCallbackRef(Cb.REM, Helpers.getLambdaArgType(remove, 0), (inst, component, bundle) -> remove.accept ((T) inst, bundle)); |
| } |
| return this; |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder add(CbBundleComponent<T> add) { |
| return callbacks(add, null, null); |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder change(CbBundleComponent<T> change) { |
| return callbacks(null, change, null); |
| } |
| |
| @Override |
| public <T> BundleDependencyBuilder remove(CbBundleComponent<T> remove) { |
| return callbacks(null, null, remove); |
| } |
| |
| private <T> BundleDependencyBuilder callbacks(CbBundleComponent<T> add, CbBundleComponent<T> change, CbBundleComponent<T> remove) { |
| if (add != null) { |
| setComponentCallbackRef(Cb.ADD, Helpers.getLambdaArgType(add, 0), (inst, component, bundle) -> add.accept ((T) inst, bundle, component)); |
| } |
| if (change != null) { |
| setComponentCallbackRef(Cb.CHG, Helpers.getLambdaArgType(change, 0), (inst, component, bundle) -> change.accept ((T) inst, bundle, component)); |
| } |
| if (remove != null) { |
| setComponentCallbackRef(Cb.REM, Helpers.getLambdaArgType(remove, 0), (inst, component, bundle) -> remove.accept ((T) inst, bundle, component)); |
| } |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder add(InstanceCbBundle add) { |
| return callbacks(add, null, null); |
| } |
| |
| @Override |
| public BundleDependencyBuilder change(InstanceCbBundle change) { |
| return callbacks(null, change, null); |
| } |
| |
| @Override |
| public BundleDependencyBuilder remove(InstanceCbBundle remove) { |
| return callbacks(null, null, remove); |
| } |
| |
| private BundleDependencyBuilder callbacks(InstanceCbBundle add, InstanceCbBundle change, InstanceCbBundle remove) { |
| if (add != null) setInstanceCallbackRef(Cb.ADD, (inst, component, bundle) -> add.accept(bundle)); |
| if (change != null) setInstanceCallbackRef(Cb.CHG, (inst, component, bundle) -> change.accept(bundle)); |
| if (remove != null) setInstanceCallbackRef(Cb.REM, (inst, component, bundle) -> remove.accept(bundle)); |
| return this; |
| } |
| |
| @Override |
| public BundleDependencyBuilder add(InstanceCbBundleComponent add) { |
| return callbacks(add, null, null); |
| } |
| |
| @Override |
| public BundleDependencyBuilder change(InstanceCbBundleComponent add) { |
| return callbacks(add, null, null); |
| } |
| |
| @Override |
| public BundleDependencyBuilder remove(InstanceCbBundleComponent remove) { |
| return callbacks(null, null, remove); |
| } |
| |
| private BundleDependencyBuilder callbacks(InstanceCbBundleComponent add, InstanceCbBundleComponent change, InstanceCbBundleComponent remove) { |
| if (add != null) setInstanceCallbackRef(Cb.ADD, (inst, component, bundle) -> add.accept(bundle, component)); |
| if (change != null) setInstanceCallbackRef(Cb.CHG, (inst, component, bundle) -> change.accept(bundle, component)); |
| if (remove != null) setInstanceCallbackRef(Cb.REM, (inst, component, bundle) -> remove.accept(bundle, component)); |
| return this; |
| } |
| |
| @Override |
| public BundleDependency build() { |
| DependencyManager dm = m_component.getDependencyManager(); |
| |
| BundleDependency dep = dm.createBundleDependency(); |
| dep.setRequired(m_required); |
| |
| if (m_filter != null) { |
| dep.setFilter(m_filter); |
| } |
| |
| if (m_bundle != null) { |
| dep.setBundle(m_bundle); |
| } |
| |
| if (m_stateMask != -1) { |
| dep.setStateMask(m_stateMask); |
| } |
| |
| if (m_propagate) { |
| dep.setPropagate(true); |
| } else if (m_propagateInstance != null) { |
| dep.setPropagate(m_propagateInstance, m_propagateMethod); |
| } else if (m_propagateCallback != null) { |
| dep.setPropagate(new Propagate(), "propagate"); |
| } |
| |
| if (m_added != null || m_changed != null || m_removed != null) { |
| dep.setCallbacks(m_instance, m_added, m_changed, m_removed); |
| } else if (m_refs.size() > 0) { |
| Object cb = createCallbackInstance(); |
| dep.setCallbacks(cb, "add", "change", "remove"); |
| } |
| |
| dep.setAutoConfig(m_autoConfig); |
| return dep; |
| } |
| |
| private <T> BundleDependencyBuilder setInstanceCallbackRef(Cb cbType, MethodRef<T> ref) { |
| requiresNoStringCallbacks(); |
| if (! m_autoConfigInvoked) m_autoConfig = false; |
| List<MethodRef<Object>> list = m_refs.computeIfAbsent(cbType, l -> new ArrayList<>()); |
| list.add((instance, component, bundle) -> ref.accept(null, component, bundle)); |
| return this; |
| } |
| |
| private <T> BundleDependencyBuilder setComponentCallbackRef(Cb cbType, Class<T> type, MethodRef<T> ref) { |
| requiresNoStringCallbacks(); |
| if (! m_autoConfigInvoked) m_autoConfig = false; |
| List<MethodRef<Object>> list = m_refs.computeIfAbsent(cbType, l -> new ArrayList<>()); |
| list.add((instance, component, bundle) -> { |
| Object componentImpl = Stream.of(component.getInstances()) |
| .filter(impl -> Helpers.getClass(impl).equals(type)) |
| .findFirst() |
| .orElseThrow(() -> new IllegalStateException("The method reference " + ref + " does not match any available component impl classes.")); |
| ref.accept((T) componentImpl, component, bundle); |
| }); |
| return this; |
| } |
| |
| @SuppressWarnings("unused") |
| private Object createCallbackInstance() { |
| Object cb = null; |
| |
| cb = new Object() { |
| void add(Component c, Bundle bundle) { |
| invokeMethodRefs(Cb.ADD, c, bundle); |
| } |
| |
| void change(Component c, Bundle bundle) { |
| invokeMethodRefs(Cb.CHG, c, bundle); |
| } |
| |
| void remove(Component c, Bundle bundle) { |
| invokeMethodRefs(Cb.REM, c, bundle); |
| } |
| }; |
| |
| return cb; |
| } |
| |
| private void invokeMethodRefs(Cb cbType, Component c, Bundle bundle) { |
| m_refs.computeIfPresent(cbType, (k, mrefs) -> { |
| mrefs.forEach(mref -> mref.accept(null, c, bundle)); |
| return mrefs; |
| }); |
| } |
| |
| private void requiresNoStringCallbacks() { |
| if (m_added != null || m_changed != null || m_removed != null) { |
| throw new IllegalStateException("can't mix method references and string callbacks."); |
| } |
| } |
| |
| private void requiresNoMethodRefs() { |
| if (m_refs.size() > 0) { |
| throw new IllegalStateException("can't mix method references and string callbacks."); |
| } |
| } |
| } |