/** * * Copyright 2019-2021 Florian Schmaus * * 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 org.jivesoftware.smack.c2s; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.jivesoftware.smack.ConnectionConfiguration; import org.jivesoftware.smack.SmackConfiguration; import org.jivesoftware.smack.fsm.StateDescriptor; import org.jivesoftware.smack.fsm.StateDescriptorGraph; import org.jivesoftware.smack.fsm.StateDescriptorGraph.GraphVertex; import org.jivesoftware.smack.util.CollectionUtil; public final class ModularXmppClientToServerConnectionConfiguration extends ConnectionConfiguration { final Set moduleDescriptors; final GraphVertex initialStateDescriptorVertex; private ModularXmppClientToServerConnectionConfiguration(Builder builder) { super(builder); moduleDescriptors = Collections.unmodifiableSet(CollectionUtil.newSetWith(builder.modulesDescriptors.values())); Set> backwardEdgeStateDescriptors = new HashSet<>(); // Add backward edges from configured connection modules. Note that all state descriptors from module // descriptors are backwards edges. for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) { Set> moduleStateDescriptors = moduleDescriptor.getStateDescriptors(); backwardEdgeStateDescriptors.addAll(moduleStateDescriptors); } try { initialStateDescriptorVertex = StateDescriptorGraph.constructStateDescriptorGraph(backwardEdgeStateDescriptors, builder.failOnUnknownStates); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException e) { // TODO: Depending on the exact exception thrown, this potentially indicates an invalid connection // configuration, e.g. there is no edge from disconnected to connected. throw new IllegalStateException(e); } for (ModularXmppClientToServerConnectionModuleDescriptor moduleDescriptor : moduleDescriptors) { moduleDescriptor.validateConfiguration(this); } } public void printStateGraphInDotFormat(PrintWriter pw, boolean breakStateName) { StateDescriptorGraph.stateDescriptorGraphToDot(Collections.singleton(initialStateDescriptorVertex), pw, breakStateName); } public String getStateGraphInDotFormat() { StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); printStateGraphInDotFormat(pw, true); return sw.toString(); } public static Builder builder() { return new Builder(); } public static final class Builder extends ConnectionConfiguration.Builder { private final Map, ModularXmppClientToServerConnectionModuleDescriptor> modulesDescriptors = new HashMap<>(); private boolean failOnUnknownStates; private Builder() { SmackConfiguration.addAllKnownModulesTo(this); } @Override public ModularXmppClientToServerConnectionConfiguration build() { return new ModularXmppClientToServerConnectionConfiguration(this); } public void addModule(ModularXmppClientToServerConnectionModuleDescriptor connectionModule) { Class moduleDescriptorClass = connectionModule.getClass(); if (modulesDescriptors.containsKey(moduleDescriptorClass)) { throw new IllegalArgumentException("A connection module for " + moduleDescriptorClass + " is already configured"); } modulesDescriptors.put(moduleDescriptorClass, connectionModule); } @SuppressWarnings("unchecked") public Builder addModule(Class moduleClass) { Class[] declaredClasses = moduleClass.getDeclaredClasses(); Class builderClass = null; for (Class declaredClass : declaredClasses) { if (!ModularXmppClientToServerConnectionModuleDescriptor.Builder.class.isAssignableFrom(declaredClass)) { continue; } builderClass = (Class) declaredClass; break; } if (builderClass == null) { throw new IllegalArgumentException( "Found no builder for " + moduleClass + ". Delcared classes: " + Arrays.toString(declaredClasses)); } return with(builderClass).buildModule(); } public B with( Class moduleDescriptorBuilderClass) { Constructor moduleDescriptorBuilderCosntructor; try { moduleDescriptorBuilderCosntructor = moduleDescriptorBuilderClass.getDeclaredConstructor( ModularXmppClientToServerConnectionConfiguration.Builder.class); } catch (NoSuchMethodException | SecurityException e) { throw new IllegalArgumentException(e); } moduleDescriptorBuilderCosntructor.setAccessible(true); B moduleDescriptorBuilder; try { moduleDescriptorBuilder = moduleDescriptorBuilderCosntructor.newInstance(this); } catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { throw new IllegalArgumentException(e); } return moduleDescriptorBuilder; } public Builder removeModule(Class moduleClass) { modulesDescriptors.remove(moduleClass); return getThis(); } public Builder removeAllModules() { modulesDescriptors.clear(); return getThis(); } /** * Fail if there are unknown states in Smack's state descriptor graph. This method is used mostly for testing * the internals of Smack. Users can safely ignore it. * * @return a reference to this builder. */ public Builder failOnUnknownStates() { failOnUnknownStates = true; return getThis(); } @Override protected Builder getThis() { return this; } } }