package games.strategy.engine.message.unifiedmessenger; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicLong; import games.strategy.debug.ClientLogger; import games.strategy.engine.message.MessageContext; import games.strategy.engine.message.RemoteMethodCall; import games.strategy.engine.message.RemoteMethodCallResults; import games.strategy.net.INode; /** * This is where the methods finally get called. * An endpoint contains the implementors for a given name that are local to this * node. * You can invoke the method and get the results for all the implementors. */ class EndPoint { // the next number we are going to give private final AtomicLong m_nextGivenNumber = new AtomicLong(); // the next number we can run private long m_currentRunnableNumber = 0; private final Object m_numberMutext = new Object(); private final Object m_implementorsMutext = new Object(); private final String m_name; private final Class<?> m_remoteClass; private final List<Object> m_implementors = new ArrayList<>(); private final boolean m_singleThreaded; public EndPoint(final String name, final Class<?> remoteClass, final boolean singleThreaded) { m_name = name; m_remoteClass = remoteClass; m_singleThreaded = singleThreaded; } public Object getFirstImplementor() { synchronized (m_implementorsMutext) { if (m_implementors.size() != 1) { throw new IllegalStateException("Invalid implementor count, " + m_implementors); } return m_implementors.get(0); } } public long takeANumber() { return m_nextGivenNumber.getAndIncrement(); } private void waitTillCanBeRun(final long aNumber) { synchronized (m_numberMutext) { while (aNumber > m_currentRunnableNumber) { try { m_numberMutext.wait(); } catch (final InterruptedException e) { ClientLogger.logQuietly(e); } } } } private void releaseNumber() { synchronized (m_numberMutext) { m_currentRunnableNumber++; m_numberMutext.notifyAll(); } } /** * @return is this the first implementor. */ public boolean addImplementor(final Object implementor) { if (!m_remoteClass.isAssignableFrom(implementor.getClass())) { throw new IllegalArgumentException(m_remoteClass + " is not assignable from " + implementor.getClass()); } synchronized (m_implementorsMutext) { final boolean rVal = m_implementors.isEmpty(); m_implementors.add(implementor); return rVal; } } public boolean isSingleThreaded() { return m_singleThreaded; } public boolean hasImplementors() { synchronized (m_implementorsMutext) { return !m_implementors.isEmpty(); } } public int getLocalImplementorCount() { synchronized (m_implementorsMutext) { return m_implementors.size(); } } /** * @return we have no more implementors. */ boolean removeImplementor(final Object implementor) { synchronized (m_implementorsMutext) { if (!m_implementors.remove(implementor)) { throw new IllegalStateException("Not removed, impl:" + implementor + " have " + m_implementors); } return m_implementors.isEmpty(); } } public String getName() { return m_name; } public Class<?> getRemoteClass() { return m_remoteClass; } /* * @param number - like the number you get in a bank line, if we are single * threaded, then the method will not run until the number comes up. Acquire * with getNumber() @return a List of RemoteMethodCallResults */ public List<RemoteMethodCallResults> invokeLocal(final RemoteMethodCall call, final long number, final INode messageOriginator) { try { if (m_singleThreaded) { waitTillCanBeRun(number); } return invokeMultiple(call, messageOriginator); } finally { releaseNumber(); } } private List<RemoteMethodCallResults> invokeMultiple(final RemoteMethodCall call, final INode messageOriginator) { // copy the implementors List<Object> implementorsCopy; synchronized (m_implementorsMutext) { implementorsCopy = new ArrayList<>(m_implementors); } final List<RemoteMethodCallResults> results = new ArrayList<>(implementorsCopy.size()); for (final Object implementor : implementorsCopy) { results.add(invokeSingle(call, implementor, messageOriginator)); } return results; } private RemoteMethodCallResults invokeSingle(final RemoteMethodCall call, final Object implementor, final INode messageOriginator) { call.resolve(m_remoteClass); Method method; try { method = implementor.getClass().getMethod(call.getMethodName(), call.getArgTypes()); method.setAccessible(true); } catch (final SecurityException | NoSuchMethodException e) { ClientLogger.logQuietly(e); throw new IllegalStateException(e.getMessage()); } MessageContext.setSenderNodeForThread(messageOriginator); try { final Object methodRVal = method.invoke(implementor, call.getArgs()); return new RemoteMethodCallResults(methodRVal); } catch (final InvocationTargetException e) { return new RemoteMethodCallResults(e.getTargetException()); } catch (final IllegalAccessException e) { ClientLogger.logQuietly("error in call:" + call, e); return new RemoteMethodCallResults(e); } catch (final IllegalArgumentException e) { ClientLogger.logQuietly("error in call:" + call, e); return new RemoteMethodCallResults(e); } finally { MessageContext.setSenderNodeForThread(null); } } public boolean equivalent(final EndPoint other) { if (other.m_singleThreaded != this.m_singleThreaded) { return false; } if (!other.m_name.equals(this.m_name)) { return false; } return other.m_remoteClass.equals(m_remoteClass); } @Override public String toString() { return "Name:" + m_name + " singleThreaded:" + m_singleThreaded + " implementors:" + m_implementors; } }