001/** 002 * 003 * Copyright 2018 Florian Schmaus 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.jivesoftware.smack.fsm; 018 019import java.lang.reflect.Constructor; 020import java.lang.reflect.InvocationTargetException; 021import java.util.Collections; 022import java.util.HashSet; 023import java.util.Set; 024import java.util.logging.Logger; 025 026import org.jivesoftware.smack.fsm.AbstractXmppStateMachineConnection.State; 027 028public abstract class StateDescriptor { 029 030 private static final Logger LOGGER = Logger.getLogger(StateDescriptor.class.getName()); 031 032 private final String stateName; 033 private final int xepNum; 034 private final String rfcSection; 035 036 private final Class<? extends AbstractXmppStateMachineConnection.State> stateClass; 037 private final Constructor<? extends AbstractXmppStateMachineConnection.State> stateClassConstructor; 038 039 private final Set<Class<? extends StateDescriptor>> successors = new HashSet<>(); 040 041 private final Set<Class<? extends StateDescriptor>> predecessors = new HashSet<>(); 042 043 private final Set<Class<? extends StateDescriptor>> precedenceOver = new HashSet<>(); 044 045 private final Set<Class<? extends StateDescriptor>> inferiorTo = new HashSet<>(); 046 047 protected StateDescriptor() { 048 this(AbstractXmppStateMachineConnection.NoOpState.class); 049 } 050 051 protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass) { 052 this(stateClass, -1, null); 053 } 054 055 protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum) { 056 this(stateClass, xepNum, null); 057 } 058 059 protected StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, String rfcSection) { 060 this(stateClass, -1, rfcSection); 061 } 062 063 @SuppressWarnings("unchecked") 064 private StateDescriptor(Class<? extends AbstractXmppStateMachineConnection.State> stateClass, int xepNum, String rfcSection) { 065 this.stateClass = stateClass; 066 if (rfcSection != null && xepNum > 0) { 067 throw new IllegalArgumentException("Must specify either RFC or XEP"); 068 } 069 this.xepNum = xepNum; 070 this.rfcSection = rfcSection; 071 072 Constructor<? extends AbstractXmppStateMachineConnection.State> selectedConstructor = null; 073 Constructor<?>[] constructors = stateClass.getDeclaredConstructors(); 074 for (Constructor<?> constructor : constructors) { 075 Class<?>[] parameterTypes = constructor.getParameterTypes(); 076 if (parameterTypes.length != 2) { 077 LOGGER.warning("Invalid State class constructor: " + constructor); 078 continue; 079 } 080 if (!AbstractXmppStateMachineConnection.class.isAssignableFrom(parameterTypes[0])) { 081 continue; 082 } 083 selectedConstructor = (Constructor<? extends State>) constructor; 084 break; 085 } 086 087 if (selectedConstructor == null) { 088 throw new IllegalArgumentException(); 089 } 090 stateClassConstructor = selectedConstructor; 091 stateClassConstructor.setAccessible(true); 092 093 String className = getClass().getSimpleName(); 094 stateName = className.replaceFirst("StateDescriptor", ""); 095 } 096 097 protected void addSuccessor(Class<? extends StateDescriptor> successor) { 098 addAndCheckNonExistent(successors, successor); 099 } 100 101 protected void addPredeccessor(Class<? extends StateDescriptor> predeccessor) { 102 addAndCheckNonExistent(predecessors, predeccessor); 103 } 104 105 protected void declarePrecedenceOver(Class<? extends StateDescriptor> subordinate) { 106 addAndCheckNonExistent(precedenceOver, subordinate); 107 } 108 109 protected void declareInferiortyTo(Class<? extends StateDescriptor> superior) { 110 addAndCheckNonExistent(inferiorTo, superior); 111 } 112 113 private static <E> void addAndCheckNonExistent(Set<E> set, E e) { 114 boolean newElement = set.add(e); 115 if (!newElement) { 116 throw new IllegalArgumentException("Element already exists in set"); 117 } 118 } 119 120 public Set<Class<? extends StateDescriptor>> getSuccessors() { 121 return Collections.unmodifiableSet(successors); 122 } 123 124 public Set<Class<? extends StateDescriptor>> getPredeccessors() { 125 return Collections.unmodifiableSet(predecessors); 126 } 127 128 public Set<Class<? extends StateDescriptor>> getSubordinates() { 129 return Collections.unmodifiableSet(precedenceOver); 130 } 131 132 public Set<Class<? extends StateDescriptor>> getSuperiors() { 133 return Collections.unmodifiableSet(inferiorTo); 134 } 135 136 public String getStateName() { 137 return stateName; 138 } 139 140 public String getFullStateName(boolean breakStateName) { 141 String reference = getReference(); 142 if (reference != null) { 143 char sep; 144 if (breakStateName) { 145 sep = '\n'; 146 } else { 147 sep = ' '; 148 } 149 return getStateName() + sep + '(' + reference + ')'; 150 } 151 else { 152 return getStateName(); 153 } 154 } 155 156 private transient String referenceCache; 157 158 public String getReference() { 159 if (referenceCache == null) { 160 if (xepNum > 0) { 161 referenceCache = "XEP-" + String.format("%04d", xepNum); 162 } else if (rfcSection != null) { 163 referenceCache = rfcSection; 164 } 165 } 166 return referenceCache; 167 } 168 169 public Class<? extends AbstractXmppStateMachineConnection.State> getStateClass() { 170 return stateClass; 171 } 172 173 protected final AbstractXmppStateMachineConnection.State constructState(AbstractXmppStateMachineConnection connection) { 174 try { 175 return stateClassConstructor.newInstance(connection, this); 176 } catch (InstantiationException | IllegalAccessException | IllegalArgumentException 177 | InvocationTargetException e) { 178 throw new IllegalStateException(e); 179 } 180 } 181 182 @Override 183 public String toString() { 184 return "StateDescriptor " + stateName; 185 } 186}