StateDescriptor.java

  1. /**
  2.  *
  3.  * Copyright 2018-2020 Florian Schmaus
  4.  *
  5.  * Licensed under the Apache License, Version 2.0 (the "License");
  6.  * you may not use this file except in compliance with the License.
  7.  * You may obtain a copy of the License at
  8.  *
  9.  *     http://www.apache.org/licenses/LICENSE-2.0
  10.  *
  11.  * Unless required by applicable law or agreed to in writing, software
  12.  * distributed under the License is distributed on an "AS IS" BASIS,
  13.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14.  * See the License for the specific language governing permissions and
  15.  * limitations under the License.
  16.  */
  17. package org.jivesoftware.smack.fsm;

  18. import java.lang.reflect.Constructor;
  19. import java.lang.reflect.InvocationTargetException;
  20. import java.util.Arrays;
  21. import java.util.Collections;
  22. import java.util.HashSet;
  23. import java.util.Set;
  24. import java.util.logging.Level;
  25. import java.util.logging.Logger;

  26. import org.jivesoftware.smack.c2s.ModularXmppClientToServerConnection;
  27. import org.jivesoftware.smack.c2s.internal.ModularXmppClientToServerConnectionInternal;

  28. public abstract class StateDescriptor {

  29.     private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName());

  30.     public enum Property {
  31.         multiVisitState,
  32.         finalState,
  33.         notImplemented,
  34.     }

  35.     private final String stateName;
  36.     private final int xepNum;
  37.     private final String rfcSection;
  38.     private final Set<Property> properties;

  39.     private final Class<? extends State> stateClass;
  40.     private final Constructor<? extends State> stateClassConstructor;

  41.     private final Set<Class<? extends StateDescriptor>> successors = new HashSet<>();

  42.     private final Set<Class<? extends StateDescriptor>> predecessors = new HashSet<>();

  43.     private final Set<Class<? extends StateDescriptor>> precedenceOver = new HashSet<>();

  44.     private final Set<Class<? extends StateDescriptor>> inferiorTo = new HashSet<>();

  45.     protected StateDescriptor() {
  46.         this(NoOpState.class, (Property) null);
  47.     }

  48.     protected StateDescriptor(Property... properties) {
  49.         this(NoOpState.class, properties);
  50.     }

  51.     protected StateDescriptor(Class<? extends State> stateClass) {
  52.         this(stateClass, -1, null, Collections.emptySet());
  53.     }

  54.     protected StateDescriptor(Class<? extends State> stateClass, Property... properties) {
  55.         this(stateClass, -1, null, new HashSet<>(Arrays.asList(properties)));
  56.     }

  57.     protected StateDescriptor(Class<? extends State> stateClass, int xepNum) {
  58.         this(stateClass, xepNum, null, Collections.emptySet());
  59.     }

  60.     protected StateDescriptor(Class<? extends State> stateClass, int xepNum,
  61.                     Property... properties) {
  62.         this(stateClass, xepNum, null, new HashSet<>(Arrays.asList(properties)));
  63.     }

  64.     protected StateDescriptor(Class<? extends State> stateClass, String rfcSection) {
  65.         this(stateClass, -1, rfcSection, Collections.emptySet());
  66.     }

  67.     @SuppressWarnings("unchecked")
  68.     private StateDescriptor(Class<? extends State> stateClass, int xepNum,
  69.                     String rfcSection, Set<Property> properties) {
  70.         this.stateClass = stateClass;
  71.         if (rfcSection != null && xepNum > 0) {
  72.             throw new IllegalArgumentException("Must specify either RFC or XEP");
  73.         }
  74.         this.xepNum = xepNum;
  75.         this.rfcSection = rfcSection;
  76.         this.properties = properties;

  77.         Constructor<? extends State> selectedConstructor = null;
  78.         Constructor<?>[] constructors = stateClass.getDeclaredConstructors();
  79.         for (Constructor<?> constructor : constructors) {
  80.             Class<?>[] parameterTypes = constructor.getParameterTypes();
  81.             if (parameterTypes.length != 3) {
  82.                 continue;
  83.             }
  84.             if (!ModularXmppClientToServerConnection.class.isAssignableFrom(parameterTypes[0])) {
  85.                 continue;
  86.             }
  87.             if (!StateDescriptor.class.isAssignableFrom(parameterTypes[1])) {
  88.                 continue;
  89.             }
  90.             if (!ModularXmppClientToServerConnectionInternal.class.isAssignableFrom(parameterTypes[2])) {
  91.                 continue;
  92.             }
  93.             selectedConstructor = (Constructor<? extends State>) constructor;
  94.             break;
  95.         }

  96.         stateClassConstructor = selectedConstructor;
  97.         if (stateClassConstructor != null) {
  98.             stateClassConstructor.setAccessible(true);
  99.         } else {
  100.             // TODO: Add validation check that if stateClassConstructor is 'null' the constructState() method is overridden.
  101.         }

  102.         String className = getClass().getSimpleName();
  103.         stateName = className.replaceFirst("StateDescriptor", "");
  104.     }

  105.     protected void addSuccessor(Class<? extends StateDescriptor> successor) {
  106.         addAndCheckNonExistent(successors, successor);
  107.     }

  108.     public void addPredeccessor(Class<? extends StateDescriptor> predeccessor) {
  109.         addAndCheckNonExistent(predecessors, predeccessor);
  110.     }

  111.     protected void declarePrecedenceOver(Class<? extends StateDescriptor> subordinate) {
  112.         addAndCheckNonExistent(precedenceOver, subordinate);
  113.     }

  114.     protected void declarePrecedenceOver(String subordinate) {
  115.         addAndCheckNonExistent(precedenceOver, subordinate);
  116.     }

  117.     protected void declareInferiorityTo(Class<? extends StateDescriptor> superior) {
  118.         addAndCheckNonExistent(inferiorTo, superior);
  119.     }

  120.     protected void declareInferiorityTo(String superior) {
  121.         addAndCheckNonExistent(inferiorTo, superior);
  122.     }

  123.     private static void addAndCheckNonExistent(Set<Class<? extends StateDescriptor>> set, String clazzName) {
  124.         Class<?> clazz;
  125.         try {
  126.             clazz = Class.forName(clazzName);
  127.         } catch (ClassNotFoundException e) {
  128.             // The state descriptor class is not in classpath, which probably means that the smack module is not loaded
  129.             // into the classpath. Hence, we can silently ignore that.
  130.             LOGGER.log(Level.FINEST, "Ignoring unknown state descriptor '" + clazzName + "'", e);
  131.             return;
  132.         }
  133.         if (!StateDescriptor.class.isAssignableFrom(clazz)) {
  134.             throw new IllegalArgumentException(clazz + " is no state descriptor class");
  135.         }
  136.         Class<? extends StateDescriptor> stateDescriptorClass = clazz.asSubclass(StateDescriptor.class);
  137.         addAndCheckNonExistent(set, stateDescriptorClass);
  138.     }

  139.     private static <E> void addAndCheckNonExistent(Set<E> set, E e) {
  140.         boolean newElement = set.add(e);
  141.         if (!newElement) {
  142.             throw new IllegalArgumentException("Element already exists in set");
  143.         }
  144.     }

  145.     public Set<Class<? extends StateDescriptor>> getSuccessors() {
  146.         return Collections.unmodifiableSet(successors);
  147.     }

  148.     public Set<Class<? extends StateDescriptor>> getPredeccessors() {
  149.         return Collections.unmodifiableSet(predecessors);
  150.     }

  151.     public Set<Class<? extends StateDescriptor>> getSubordinates() {
  152.         return Collections.unmodifiableSet(precedenceOver);
  153.     }

  154.     public Set<Class<? extends StateDescriptor>> getSuperiors() {
  155.         return Collections.unmodifiableSet(inferiorTo);
  156.     }

  157.     public String getStateName() {
  158.         return stateName;
  159.     }

  160.     public String getFullStateName(boolean breakStateName) {
  161.         String reference = getReference();
  162.         if (reference != null) {
  163.             char sep;
  164.             if (breakStateName) {
  165.                 sep = '\n';
  166.             } else {
  167.                 sep = ' ';
  168.             }
  169.             return getStateName() + sep + '(' + reference + ')';
  170.         }
  171.         else {
  172.             return getStateName();
  173.         }
  174.     }

  175.     private transient String referenceCache;

  176.     public String getReference()  {
  177.         if (referenceCache == null) {
  178.             if (xepNum > 0) {
  179.                 referenceCache = "XEP-" + String.format("%04d", xepNum);
  180.             } else if (rfcSection != null) {
  181.                 referenceCache = rfcSection;
  182.             }
  183.         }
  184.         return referenceCache;
  185.     }

  186.     public Class<? extends State> getStateClass() {
  187.         return stateClass;
  188.     }

  189.     public boolean isMultiVisitState() {
  190.         return properties.contains(Property.multiVisitState);
  191.     }

  192.     public boolean isNotImplemented() {
  193.         return properties.contains(Property.notImplemented);
  194.     }

  195.     public boolean isFinalState() {
  196.         return properties.contains(Property.finalState);
  197.     }

  198.     protected State constructState(ModularXmppClientToServerConnectionInternal connectionInternal) {
  199.         ModularXmppClientToServerConnection connection = connectionInternal.connection;
  200.         try {
  201.             // If stateClassConstructor is null here, then you probably forgot to override the
  202.             // StateDescriptor.constructState() method?
  203.             return stateClassConstructor.newInstance(connection, this, connectionInternal);
  204.         } catch (InstantiationException | IllegalAccessException | IllegalArgumentException
  205.                         | InvocationTargetException e) {
  206.             throw new IllegalStateException(e);
  207.         }
  208.     }

  209.     @Override
  210.     public String toString() {
  211.         return "StateDescriptor " + stateName;
  212.     }
  213. }