summaryrefslogtreecommitdiff
path: root/platform/remote-servers/impl/src/com/intellij/remoteServer/agent/impl/ThreadInvocationHandler.java
blob: 3e260227abe193454d70a93d1c0d8b2e05b7b116 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
package com.intellij.remoteServer.agent.impl;

import com.intellij.openapi.diagnostic.Logger;
import com.intellij.remoteServer.agent.annotation.AsyncCall;
import com.intellij.remoteServer.agent.annotation.ChildCall;
import com.intellij.remoteServer.agent.annotation.FinalCall;
import com.intellij.remoteServer.agent.annotation.ImmediateCall;
import com.intellij.remoteServer.agent.impl.util.FinalTask;
import com.intellij.remoteServer.agent.impl.util.SequentialTaskExecutor;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.Nullable;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Map;
import java.util.concurrent.Callable;

/**
 * @author michael.golubev
 */
public class ThreadInvocationHandler implements InvocationHandler {

  private static final Logger LOG = Logger.getInstance("#" + ThreadInvocationHandler.class.getName());

  private final SequentialTaskExecutor myTaskExecutor;
  private final ClassLoader myCallerClassLoader;
  private final Object myTarget;
  private final ChildWrapperCreator myPreWrapperFactory;

  private Map<Object, Object> myChild2Wrapped;

  public ThreadInvocationHandler(SequentialTaskExecutor taskExecutor, ClassLoader callerClassLoader, Object target) {
    this(taskExecutor, callerClassLoader, target, null);
  }

  public ThreadInvocationHandler(SequentialTaskExecutor taskExecutor, ClassLoader callerClassLoader, Object target,
                                 @Nullable ChildWrapperCreator preWrapperCreator) {
    myTaskExecutor = taskExecutor;
    myCallerClassLoader = callerClassLoader;
    myTarget = target;
    myPreWrapperFactory = preWrapperCreator;
    myChild2Wrapped = new HashMap<Object, Object>();
  }

  @Override
  public Object invoke(Object proxy, final Method method, final Object[] args) throws Throwable {
    final Callable<Object> taskCallable = new Callable<Object>() {

      @Override
      public Object call() {
        try {
          return method.invoke(myTarget, args);
        }
        catch (IllegalAccessException e) {
          LOG.error(e);
          return null;
        }
        catch (InvocationTargetException e) {
          LOG.error(e);
          return null;
        }
      }
    };

    try {
      boolean immediateCall = method.getAnnotation(ImmediateCall.class) != null;

      boolean childCall = method.getAnnotation(ChildCall.class) != null;
      if (childCall) {
        Object child = immediateCall ? taskCallable.call() : myTaskExecutor.queueAndWaitTask(taskCallable);
        if (child == null) {
          return null;
        }

        Object cached = myChild2Wrapped.get(child);
        if (cached != null) {
          return cached;
        }

        Class<?> childClass = child.getClass();
        Class<?>[] childInterfaces = childClass.getInterfaces();
        LOG.assertTrue(childInterfaces.length == 1, "Child class is expected to implement single child interface");
        Class<?> childInterface = childInterfaces[0];

        Class<?> callerChildInterface;
        try {
          callerChildInterface = myCallerClassLoader.loadClass(childInterface.getName());
        }
        catch (ClassNotFoundException e) {
          LOG.error(e);
          return null;
        }

        Object preWrappedChild;
        if (myPreWrapperFactory == null) {
          preWrappedChild = child;
        }
        else {
          preWrappedChild = Proxy.newProxyInstance(myCallerClassLoader,
                                                   new Class[]{callerChildInterface},
                                                   myPreWrapperFactory.createWrapperInvocationHandler(child));
        }

        Object result = Proxy.newProxyInstance(myCallerClassLoader,
                                               new Class[]{callerChildInterface},
                                               new ThreadInvocationHandler(myTaskExecutor, myCallerClassLoader, preWrappedChild,
                                                                           myPreWrapperFactory));

        myChild2Wrapped.put(child, result);
        return result;
      }

      if (immediateCall) {
        return taskCallable.call();
      }

      boolean asyncCall = method.getAnnotation(AsyncCall.class) != null;

      if (asyncCall) {
        myTaskExecutor.queueTask(new Runnable() {

          @Override
          public void run() {
            try {
              taskCallable.call();
            }
            catch (Exception e) {
              LOG.error(e); // should never happen
            }
          }
        });
        return null;
      }
      else {
        return myTaskExecutor.queueAndWaitTask(taskCallable);
      }
    }
    finally {
      boolean finalCall = method.getAnnotation(FinalCall.class) != null;
      if (finalCall) {
        myTaskExecutor.queueTask(new FinalTask() {

          @Override
          public void run() {

          }
        });
      }
    }
  }
}