public List<String> onTabComplete(
      CommandSender commandSender, Command command, String s, String[] strings) {

    for (TabCompleter completer : delegateCompleters) {
      List<String> list = completer.onTabComplete(commandSender, command, s, strings);
      if (list != null) return list;
    }

    String expression = strings[strings.length - 1];
    TreeSet<String> result = new TreeSet<String>();
    Caller caller = plugin.getCallerService().getCaller(commandSender);
    WorkspaceService service = plugin.getWorkspaceService();
    String workspaceName = service.getWorkspaceName(commandSender);
    Workspace workspace = service.getWorkspace(workspaceName);
    LinkedList<String> tokens = new LinkedList<String>();
    boolean needHelp = expression.endsWith("?");
    if (needHelp) expression = expression.substring(0, expression.length() - 1);
    Collections.addAll(tokens, expression.split("\\."));
    if (expression.endsWith(".")) tokens.add("");

    if (needHelp) {
      getHelp(caller, workspace, tokens);
      return Collections.singletonList(expression);
    }

    String firstToken = tokens.pollFirst();
    if (firstToken == null) firstToken = "";
    MetaClass callerScriptMetaClass = InvokerHelper.getMetaClass(CallerScript.class);
    MetaClass workspaceMetaClass = InvokerHelper.getMetaClass(Workspace.class);
    Map workspaceVars = null;
    if (workspace != null) workspaceVars = workspace.getBinding().getVariables();
    Map globalVars = service.getBinding().getVariables();
    PreparedScriptProperties properties = new PreparedScriptProperties();
    properties.setCaller(caller);
    properties.setServer(plugin.getServer());
    properties.setWorkspace(workspace);

    if (tokens.isEmpty()) { // get current method or class
      for (MetaProperty metaProperty : callerScriptMetaClass.getProperties()) {
        String name = metaProperty.getName();
        if (name.contains(firstToken)) result.add(name);
      }
      for (String name : service.getImportTabCompleteClasses().keySet()) {
        if (name.contains(firstToken)) result.add(name);
      }
      for (MetaMethod metaMethod : callerScriptMetaClass.getMetaMethods()) {
        if (metaMethod.getDeclaringClass().getTheClass().equals(Object.class)) continue;
        String name = metaMethod.getName();
        if (name.contains(firstToken)) {
          String methodEnd = "(";
          if (metaMethod.isValidMethod(new Class[] {Closure.class})) methodEnd = "{";
          else if (metaMethod.getParameterTypes().length == 0) methodEnd = "()";
          result.add(name + methodEnd);
        }
        int args = metaMethod.getParameterTypes().length;
        if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
            && name.length() > 3) {
          String propertyName = getPropertyName(name);
          if (propertyName != null && propertyName.contains(firstToken)) result.add(propertyName);
        }
      }
      for (MetaMethod metaMethod : workspaceMetaClass.getMetaMethods()) {
        if (metaMethod.getDeclaringClass().getTheClass().equals(Object.class)) continue;
        String name = metaMethod.getName();
        if (name.contains(firstToken)) {
          String methodEnd = "(";
          if (metaMethod.isValidMethod(new Class[] {Closure.class})) methodEnd = "{";
          else if (metaMethod.getParameterTypes().length == 0) methodEnd = "()";
          result.add(name + methodEnd);
        }
      }
      for (Method method : CallerScript.class.getMethods()) {
        if (method.getDeclaringClass().equals(Object.class)) continue;
        String name = method.getName();
        if (name.contains(firstToken)) {
          String methodEnd = "(";
          Class<?>[] types = method.getParameterTypes();
          if (types.length == 1 && Closure.class.isAssignableFrom(types[0])) methodEnd = "{";
          else if (types.length == 0) methodEnd = "()";
          result.add(name + methodEnd);
          int args = method.getParameterTypes().length;
          if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
              && name.length() > 3) {
            String propertyName = getPropertyName(name);
            if (propertyName != null && propertyName.contains(firstToken)) result.add(propertyName);
          }
        }
      }
      for (Method method : Workspace.class.getMethods()) {
        if (method.getDeclaringClass().equals(Object.class)) continue;
        String name = method.getName();
        if (name.contains(firstToken)) {
          String methodEnd = "(";
          Class<?>[] types = method.getParameterTypes();
          if (types.length == 1 && Closure.class.isAssignableFrom(types[0])) methodEnd = "{";
          else if (types.length == 0) methodEnd = "()";
          result.add(name + methodEnd);
        }
      }
      if (workspaceVars != null)
        for (Object key : workspaceVars.keySet()) {
          String name = key.toString();
          if (name.contains(firstToken)) result.add(name);
        }
      if (globalVars != null)
        for (Object key : globalVars.keySet()) {
          String name = key.toString();
          if (name.contains(firstToken)) result.add(name);
        }
      for (GroovyObject modifier : CallerScript.getDynamicModifiers()) {
        Object[] params = {properties};
        try {
          Map<?, ?> map =
              (Map) modifier.getMetaClass().invokeMethod(modifier, "getPropertyMapFor", params);
          for (Object key : map.keySet()) {
            String name = key.toString();
            if (name.contains(firstToken)) result.add(name);
          }
        } catch (Exception ignored) {
        }
        try {
          Map<?, ?> map =
              (Map) modifier.getMetaClass().invokeMethod(modifier, "getMethodMapFor", params);
          for (Object key : map.keySet()) {
            String name = key.toString();
            if (name.contains(firstToken)) result.add(name + "(");
          }
        } catch (Exception ignored) {
        }
      }
      if (globalVars != null)
        for (Object key : globalVars.keySet()) {
          String name = key.toString();
          if (name.contains(firstToken)) result.add(name);
        }
      return new ArrayList<String>(result);
    }

    // get metaclass of first token
    MetaClass metaClass =
        getFirstTokenMeta(
            caller,
            firstToken,
            commandSender,
            service,
            callerScriptMetaClass,
            workspaceMetaClass,
            workspace,
            workspaceVars,
            globalVars,
            properties);
    boolean classHook =
        tokens.size() <= 1 && service.getImportTabCompleteClasses().containsKey(firstToken);

    if (metaClass == null) return null;
    metaClass = skipTokens(tokens, metaClass);
    if (metaClass == null) return null;

    // select property or method of last metaclass
    String token = tokens.pollFirst();
    Class theClass = metaClass.getTheClass();
    String inputPrefix = expression.substring(0, expression.lastIndexOf('.')) + ".";
    for (MetaProperty metaProperty : metaClass.getProperties()) {
      String name = metaProperty.getName();
      if (name.startsWith(token)) result.add(inputPrefix + name);
    }
    for (MetaMethod metaMethod : metaClass.getMetaMethods()) {
      if (metaMethod.getDeclaringClass().getTheClass().equals(Object.class)) continue;
      String name = metaMethod.getName();
      if (name.startsWith(token)) {
        String methodEnd = "(";
        if (metaMethod.isValidMethod(new Class[] {Closure.class})) methodEnd = "{";
        else if (metaMethod.getNativeParameterTypes().length == 0) methodEnd = "()";
        result.add(inputPrefix + name + methodEnd);
      }
      int args = metaMethod.getParameterTypes().length;
      if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
          && name.length() > 3) {
        String propertyName = getPropertyName(name);
        if (propertyName != null && propertyName.startsWith(token))
          result.add(inputPrefix + propertyName);
      }
    }
    for (Method method : theClass.getMethods()) {
      if (method.getDeclaringClass().equals(Object.class)) continue;
      String name = method.getName();
      if (name.startsWith(token)) {
        String methodEnd = "(";
        Class<?>[] types = method.getParameterTypes();
        if (types.length == 1 && Closure.class.isAssignableFrom(types[0])) methodEnd = "{";
        if (types.length == 0) methodEnd = "()";
        result.add(inputPrefix + name + methodEnd);
      }
      int args = method.getParameterTypes().length;
      if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
          && name.length() > 3) {
        String propertyName = getPropertyName(name);
        if (propertyName != null && propertyName.startsWith(token))
          result.add(inputPrefix + propertyName);
      }
    }
    if (Enum.class.isAssignableFrom(theClass)) {
      Enum[] enumValues = getEnumValues(theClass);
      if (enumValues != null)
        for (Enum anEnum : enumValues) {
          String name = anEnum.name();
          if (name.startsWith(token)) result.add(inputPrefix + name);
        }
    }
    if (classHook) {
      for (MetaProperty metaProperty : InvokerHelper.getMetaClass(Class.class).getProperties()) {
        String name = metaProperty.getName();
        if (name.startsWith(token)) result.add(inputPrefix + name);
      }
      for (Method method : Class.class.getMethods()) {
        if (method.getDeclaringClass().equals(Object.class)) continue;
        String name = method.getName();
        if (name.startsWith(token)) {
          String methodEnd = "(";
          Class<?>[] types = method.getParameterTypes();
          if (types.length == 1 && Closure.class.isAssignableFrom(types[0])) methodEnd = "{";
          if (types.length == 0) methodEnd = "()";
          result.add(inputPrefix + name + methodEnd);
        }
        int args = method.getParameterTypes().length;
        if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
            && name.length() > 3) {
          String propertyName = getPropertyName(name);
          if (propertyName != null && propertyName.startsWith(token))
            result.add(inputPrefix + propertyName);
        }
      }
    }
    return new ArrayList<String>(result);
  }
    public void addGetter(MetaBeanProperty property) throws Exception {
      MetaMethod getter = property.getGetter();

      // GENERATE private boolean <prop>Set;

      String flagName = String.format("%sSet", property.getName());
      visitor.visitField(
          Opcodes.ACC_PRIVATE, flagName, Type.BOOLEAN_TYPE.getDescriptor(), null, null);

      addConventionGetter(getter.getName(), flagName, property);

      String getterName = getter.getName();
      Class<?> returnType = getter.getReturnType();

      // If it's a boolean property, there can be get or is type variants.
      // If this class has both, decorate both.
      if (returnType.equals(Boolean.TYPE)) {
        boolean getterIsIsMethod = getterName.startsWith("is");
        String propertyNameComponent = getterName.substring(getterIsIsMethod ? 2 : 3);
        String alternativeGetterName =
            String.format("%s%s", getterIsIsMethod ? "get" : "is", propertyNameComponent);

        try {
          type.getMethod(alternativeGetterName);
          addConventionGetter(alternativeGetterName, flagName, property);
        } catch (NoSuchMethodException e) {
          // ignore, no method to override
        }
      }
    }
 private MetaClass skipTokens(LinkedList<String> tokens, MetaClass metaClass) {
   while (tokens.size() > 1) {
     Class theClass = metaClass.getTheClass();
     String token = tokens.pollFirst();
     Class selectedClass = null;
     if (propertyPattern.matcher(token).matches()) {
       String getterName = "get" + StringGroovyMethods.capitalize((CharSequence) token);
       for (MetaMethod metaMethod : metaClass.getMetaMethods()) {
         if (metaMethod.getName().equals(getterName)) {
           selectedClass = metaMethod.getReturnType();
           break;
         }
       }
       if (selectedClass == null)
         for (Method method : theClass.getMethods()) {
           if (method.getParameterTypes().length != 0) continue;
           if (method.getName().equals(getterName)) {
             selectedClass = method.getReturnType();
             break;
           }
         }
       if (selectedClass == null) {
         MetaProperty metaProperty = metaClass.getMetaProperty(token);
         if (metaProperty == null) return null;
         selectedClass = metaProperty.getType();
       }
     } else if (methodPattern.matcher(token).matches()) {
       String curMethodName = token.substring(0, token.indexOf('('));
       for (MetaMethod metaMethod : metaClass.getMetaMethods()) {
         String name = metaMethod.getName();
         if (name.equals(curMethodName)) {
           selectedClass = metaMethod.getReturnType();
           break;
         }
       }
       if (selectedClass == null)
         for (Method method : theClass.getMethods()) {
           String name = method.getName();
           if (name.equals(curMethodName)) {
             selectedClass = method.getReturnType();
             break;
           }
         }
     }
     if (selectedClass == null) return null;
     metaClass = InvokerHelper.getMetaClass(selectedClass);
   }
   return metaClass;
 }
    public void addSetMethod(MetaBeanProperty property) throws Exception {
      MetaMethod setter = property.getSetter();
      Type paramType = Type.getType(setter.getParameterTypes()[0].getTheClass());
      Type returnType = Type.getType(setter.getReturnType());
      String setterDescriptor = Type.getMethodDescriptor(returnType, new Type[] {paramType});

      // GENERATE public void <propName>(<type> v) { <setter>(v) }
      String setMethodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {paramType});
      MethodVisitor methodVisitor =
          visitor.visitMethod(
              Opcodes.ACC_PUBLIC, property.getName(), setMethodDescriptor, null, new String[0]);
      methodVisitor.visitCode();

      // GENERATE <setter>(v)

      methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
      methodVisitor.visitVarInsn(paramType.getOpcode(Opcodes.ILOAD), 1);

      methodVisitor.visitMethodInsn(
          Opcodes.INVOKEVIRTUAL,
          generatedType.getInternalName(),
          setter.getName(),
          setterDescriptor);

      // END

      methodVisitor.visitInsn(Opcodes.RETURN);
      methodVisitor.visitMaxs(0, 0);
      methodVisitor.visitEnd();
    }
    public void addSetter(MetaBeanProperty property) throws Exception {
      MetaMethod setter = property.getSetter();

      // GENERATE public <return-type> <setter>(<type> v) { <return-type> v = super.<setter>(v);
      // <prop>Set = true; return v; }

      Type paramType = Type.getType(setter.getParameterTypes()[0].getTheClass());
      Type returnType = Type.getType(setter.getReturnType());
      String setterDescriptor = Type.getMethodDescriptor(returnType, new Type[] {paramType});
      MethodVisitor methodVisitor =
          visitor.visitMethod(
              Opcodes.ACC_PUBLIC, setter.getName(), setterDescriptor, null, new String[0]);
      methodVisitor.visitCode();

      // GENERATE super.<setter>(v)

      methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
      methodVisitor.visitVarInsn(paramType.getOpcode(Opcodes.ILOAD), 1);

      methodVisitor.visitMethodInsn(
          Opcodes.INVOKESPECIAL,
          superclassType.getInternalName(),
          setter.getName(),
          setterDescriptor);

      // END

      // GENERATE <prop>Set = true

      methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
      methodVisitor.visitLdcInsn(true);
      methodVisitor.visitFieldInsn(
          Opcodes.PUTFIELD,
          generatedType.getInternalName(),
          String.format("%sSet", property.getName()),
          Type.BOOLEAN_TYPE.getDescriptor());

      // END

      methodVisitor.visitInsn(returnType.getOpcode(Opcodes.IRETURN));
      methodVisitor.visitMaxs(0, 0);
      methodVisitor.visitEnd();
    }
    public void addActionMethod(MetaMethod method) throws Exception {
      Type actionImplType = Type.getType(ClosureBackedAction.class);
      Type closureType = Type.getType(Closure.class);

      String methodDescriptor = Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {closureType});

      // GENERATE public void <method>(Closure v) { <method>(new ClosureBackedAction(v)); }
      MethodVisitor methodVisitor =
          visitor.visitMethod(
              Opcodes.ACC_PUBLIC, method.getName(), methodDescriptor, null, new String[0]);
      methodVisitor.visitCode();

      // GENERATE <method>(new ClosureBackedAction(v));
      methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);

      // GENERATE new ClosureBackedAction(v);
      methodVisitor.visitTypeInsn(Opcodes.NEW, actionImplType.getInternalName());
      methodVisitor.visitInsn(Opcodes.DUP);
      methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
      String constuctorDescriptor =
          Type.getMethodDescriptor(Type.VOID_TYPE, new Type[] {closureType});
      methodVisitor.visitMethodInsn(
          Opcodes.INVOKESPECIAL, actionImplType.getInternalName(), "<init>", constuctorDescriptor);

      methodDescriptor =
          Type.getMethodDescriptor(
              Type.getType(method.getReturnType()),
              new Type[] {Type.getType(method.getParameterTypes()[0].getTheClass())});
      methodVisitor.visitMethodInsn(
          Opcodes.INVOKEVIRTUAL,
          generatedType.getInternalName(),
          method.getName(),
          methodDescriptor);

      methodVisitor.visitInsn(Opcodes.RETURN);
      methodVisitor.visitMaxs(0, 0);
      methodVisitor.visitEnd();
    }
  private MetaClass getFirstTokenMeta(
      Caller caller,
      String firstToken,
      CommandSender commandSender,
      WorkspaceService service,
      MetaClass callerScriptMetaClass,
      MetaClass workspaceMetaClass,
      Workspace workspace,
      Map workspaceVars,
      Map globalVars,
      PreparedScriptProperties properties) {

    if (firstToken.isEmpty() || propertyPattern.matcher(firstToken).matches()) {
      if (firstToken.equals("me")) return InvokerHelper.getMetaClass(commandSender.getClass());
      if (firstToken.equals("this")) return InvokerHelper.getMetaClass(CallerScript.class);
      if (firstToken.equals("_")) {
        Object result = caller.getLastResult();
        if (result == null) return null;
        return InvokerHelper.getMetaClass(result.getClass());
      }
      Class aClass = service.getImportTabCompleteClasses().get(firstToken);
      if (aClass != null) return InvokerHelper.getMetaClass(aClass);
      MetaProperty property = callerScriptMetaClass.getMetaProperty(firstToken);
      if (property != null) return InvokerHelper.getMetaClass(property.getType());
      if (workspaceVars != null)
        for (Object key : workspaceVars.keySet()) {
          String name = key.toString();
          if (name.contains(firstToken)) {
            Object val = workspaceVars.get(key);
            return InvokerHelper.getMetaClass(val.getClass());
          }
        }
      if (globalVars != null)
        for (Object key : globalVars.keySet()) {
          String name = key.toString();
          if (name.contains(firstToken)) {
            Object val = globalVars.get(key);
            return InvokerHelper.getMetaClass(val.getClass());
          }
        }
      if (workspace != null) {
        for (GroovyObject modifier : CallerScript.getDynamicModifiers()) {
          Object[] params = {properties};
          try {
            Map<?, ?> map =
                (Map) modifier.getMetaClass().invokeMethod(modifier, "getPropertyMapFor", params);
            Object value = map.get(firstToken);
            if (!(value instanceof Class)) return null;
            return InvokerHelper.getMetaClass((Class) value);
          } catch (Exception ignored) {
          }
        }
      }
    } else if (methodPattern.matcher(firstToken).matches()) {
      String curMethodName = firstToken.substring(0, firstToken.indexOf('('));
      for (MetaMethod metaMethod : callerScriptMetaClass.getMetaMethods()) {
        String name = metaMethod.getName();
        if (name.equals(curMethodName)) {
          return InvokerHelper.getMetaClass(metaMethod.getReturnType());
        }
      }
      for (MetaMethod metaMethod : workspaceMetaClass.getMetaMethods()) {
        String name = metaMethod.getName();
        if (name.equals(curMethodName)) {
          return InvokerHelper.getMetaClass(metaMethod.getReturnType());
        }
      }
      for (GroovyObject modifier : CallerScript.getDynamicModifiers()) {
        Object[] params = {properties};
        try {
          Map<?, ?> map =
              (Map) modifier.getMetaClass().invokeMethod(modifier, "getMethodMapFor", params);
          Object value = map.get(curMethodName);
          if (!(value instanceof Class)) return null;
          return InvokerHelper.getMetaClass(value);
        } catch (Exception ignored) {
        }
      }
    } else if (firstToken.endsWith("\'") || firstToken.endsWith("\"")) {
      return InvokerHelper.getMetaClass(String.class);
    }
    return null;
  }
  public void getHelp(Caller caller, Workspace workspace, LinkedList<String> tokens) {

    WorkspaceService service = plugin.getWorkspaceService();
    String source = service.getWorkspaceName(caller.getSender());
    String firstToken = tokens.pollFirst();
    if (firstToken == null) firstToken = "";
    MetaClass callerScriptMetaClass = InvokerHelper.getMetaClass(CallerScript.class);
    MetaClass workspaceMetaClass = InvokerHelper.getMetaClass(Workspace.class);
    Map workspaceVars = null;
    if (workspace != null) workspaceVars = workspace.getBinding().getVariables();
    Map globalVars = service.getBinding().getVariables();
    PreparedScriptProperties properties = new PreparedScriptProperties();
    properties.setCaller(caller);
    properties.setServer(plugin.getServer());
    properties.setWorkspace(workspace);

    if (tokens.isEmpty()) { // get current method or class
      if (firstToken.equals("_")) {
        Object result = caller.getLastResult();
        String type = "null";
        if (result != null) type = result.getClass().getName();
        caller.sendPrintMessage("Last result: " + AQUA + type + RESET, source);
        return;
      }
      MetaProperty prop = callerScriptMetaClass.getMetaProperty(firstToken);
      if (prop != null) {
        caller.sendPrintMessage(
            String.format(
                "Script property %s: %s",
                YELLOW + prop.getName() + RESET, AQUA + prop.getType().getName() + RESET),
            source);
        return;
      }
      Class compClass = service.getImportTabCompleteClasses().get(firstToken);
      if (compClass != null) {
        String type = "Class";
        if (compClass.isInterface()) type = "Interface";
        else if (compClass.isEnum()) type = "Enum class";
        StringBuilder buf = new StringBuilder();
        Class superClass = compClass.getSuperclass();
        buf.append(type).append(" ").append(YELLOW).append(compClass.getName()).append(RESET);
        if (superClass != null) {
          buf.append(" extends ").append(AQUA).append(superClass.getName()).append(RESET);
        }
        caller.sendPrintMessage(buf, source);
        return;
      }
      for (MetaMethod metaMethod : callerScriptMetaClass.getMetaMethods()) {
        String name = metaMethod.getName();
        String methodEnd = "(";
        if (metaMethod.isValidMethod(new Class[] {Closure.class})) methodEnd = "{";
        else if (metaMethod.getParameterTypes().length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Script meta method ")
              .append(YELLOW)
              .append(metaMethod.getName())
              .append(RESET);
          buf.append(" returns ").append(AQUA).append(metaMethod.getReturnType().getName());
          buf.append(RESET).append('\n');
          Class[] types = metaMethod.getNativeParameterTypes();
          buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
          for (Class type : types)
            buf.append(AQUA).append(type.getName()).append('\n').append(RESET);
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
        int args = metaMethod.getParameterTypes().length;
        if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
            && name.length() > 3) {
          String propertyName = getPropertyName(name);
          if (propertyName != null && propertyName.equals(firstToken)) {
            caller.sendPrintMessage(
                String.format(
                    "Script meta getter %s returns %s",
                    YELLOW + name + "()" + RESET,
                    AQUA + metaMethod.getReturnType().getName() + RESET),
                source);
          }
        }
      }
      for (MetaMethod metaMethod : workspaceMetaClass.getMetaMethods()) {
        String name = metaMethod.getName();
        String methodEnd = "(";
        if (metaMethod.isValidMethod(new Class[] {Closure.class})) methodEnd = "{";
        else if (metaMethod.getParameterTypes().length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Workspace meta method ")
              .append(YELLOW)
              .append(metaMethod.getName())
              .append(RESET);
          buf.append(" returns ").append(AQUA).append(metaMethod.getReturnType().getName());
          buf.append(RESET).append('\n');
          Class[] types = metaMethod.getNativeParameterTypes();
          buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
          for (Class type : types)
            buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
      }
      for (Method method : CallerScript.class.getMethods()) {
        String name = method.getName();
        String methodEnd = "(";
        Class<?>[] params = method.getParameterTypes();
        if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
        else if (params.length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Script method ").append(YELLOW).append(method.getName()).append(RESET);
          buf.append(" returns ").append(AQUA).append(method.getReturnType().getName());
          buf.append(RESET).append('\n');
          buf.append("arguments: ").append(YELLOW).append(params.length).append(RESET).append('\n');
          for (Class<?> type : params)
            buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
        int args = params.length;
        if ((name.startsWith("get") && args == 0 || name.startsWith("set") && args == 1)
            && name.length() > 3) {
          String propertyName = getPropertyName(name);
          if (propertyName != null && propertyName.equals(firstToken)) {
            caller.sendPrintMessage(
                String.format(
                    "Script getter %s returns %s",
                    YELLOW + name + "()" + RESET, AQUA + method.getReturnType().getName() + RESET),
                source);
          }
        }
      }
      for (Method method : Workspace.class.getMethods()) {
        String name = method.getName();
        String methodEnd = "(";
        Class<?>[] params = method.getParameterTypes();
        if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
        else if (params.length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Workspace method ").append(YELLOW).append(method.getName()).append(RESET);
          buf.append(" returns ").append(AQUA).append(method.getReturnType().getName());
          buf.append(RESET).append('\n');
          buf.append("arguments: ").append(YELLOW).append(params.length).append(RESET).append('\n');
          for (Class<?> type : params)
            buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
      }
      if (workspaceVars != null) {
        Object result = workspaceVars.get(firstToken);
        if (result != null || workspaceVars.containsKey(firstToken)) {
          caller.sendPrintMessage(
              String.format(
                  "Workspace variable %s: %s",
                  YELLOW + firstToken + RESET,
                  AQUA + (result == null ? "null" : result.getClass().getName()) + RESET),
              source);
          return;
        }
      }
      if (globalVars != null) {
        Object result = globalVars.get(firstToken);
        if (result != null || globalVars.containsKey(firstToken)) {
          caller.sendPrintMessage(
              String.format(
                  "Workspace variable %s: %s",
                  YELLOW + firstToken + RESET,
                  AQUA + (result == null ? "null" : result.getClass().getName()) + RESET),
              source);
          return;
        }
      }
      for (GroovyObject modifier : CallerScript.getDynamicModifiers()) {
        Object[] params = {properties};
        try {
          Map<?, ?> map =
              (Map) modifier.getMetaClass().invokeMethod(modifier, "getPropertyMapFor", params);
          Object result = map.get(firstToken);
          if (result != null || map.containsKey(firstToken)) {
            Class resultClass = result instanceof Class ? (Class) result : null;
            caller.sendPrintMessage(
                String.format(
                    "Dynamic variable %s: %s",
                    YELLOW + firstToken + RESET,
                    AQUA + (resultClass == null ? "unknown type" : resultClass.getName()) + RESET),
                source);
          }
        } catch (Exception ignored) {
        }
        try {
          Map<?, ?> map =
              (Map) modifier.getMetaClass().invokeMethod(modifier, "getMethodMapFor", params);
          String funToken = firstToken;
          if (funToken.endsWith("(")) funToken = firstToken.substring(0, funToken.length() - 1);
          Object result = map.get(funToken);
          if (result != null || map.containsKey(funToken)) {
            Class resultClass = result instanceof Class ? (Class) result : null;
            caller.sendPrintMessage(
                String.format(
                    "Dynamic function %s: %s",
                    YELLOW + firstToken + RESET,
                    AQUA + (resultClass == null ? "unknown type" : resultClass.getName()) + RESET),
                source);
          }
        } catch (Exception ignored) {
        }
      }
      return;
    }

    MetaClass metaClass =
        getFirstTokenMeta(
            caller,
            firstToken,
            caller.getSender(),
            service,
            callerScriptMetaClass,
            workspaceMetaClass,
            workspace,
            workspaceVars,
            globalVars,
            properties);
    boolean classHook =
        tokens.size() <= 1 && service.getImportTabCompleteClasses().containsKey(firstToken);

    metaClass = skipTokens(tokens, metaClass);
    if (metaClass == null) return;

    // select property or method of last metaclass
    String token = tokens.pollFirst();
    Class theClass = metaClass.getTheClass();
    MetaProperty metaProperty = metaClass.getMetaProperty(token);
    if (metaProperty != null) {
      caller.sendPrintMessage(
          String.format(
              "Meta property %s: %s",
              YELLOW + token + RESET, AQUA + metaProperty.getType().getName() + RESET),
          source);
    }
    for (MetaMethod metaMethod : metaClass.getMetaMethods()) {
      String name = metaMethod.getName();
      String methodEnd = "(";
      Class<?>[] params = metaMethod.getNativeParameterTypes();
      if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
      else if (params.length == 0) methodEnd = "()";
      if (token.equals(name) || token.equals(name + methodEnd)) {
        StringBuilder buf = new StringBuilder();
        buf.append("Meta method ").append(YELLOW).append(metaMethod.getName()).append(RESET);
        buf.append(" returns ").append(AQUA).append(metaMethod.getReturnType().getName());
        buf.append(RESET).append('\n');
        Class[] types = metaMethod.getNativeParameterTypes();
        buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
        for (Class type : types) buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
        buf.deleteCharAt(buf.length() - 1);
        caller.sendPrintMessage(buf, source);
      }
      int args = params.length;
      if (name.startsWith("get") && args == 0 && name.length() > 3) {
        String propertyName = getPropertyName(name);
        if (propertyName != null && propertyName.equals(token)) {
          caller.sendPrintMessage(
              String.format(
                  "Meta getter %s returns %s",
                  YELLOW + name + "()" + RESET,
                  AQUA + metaMethod.getReturnType().getName() + RESET),
              source);
        }
      }
    }
    for (Method method : theClass.getMethods()) {
      String name = method.getName();
      String methodEnd = "(";
      Class<?>[] params = method.getParameterTypes();
      if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
      else if (params.length == 0) methodEnd = "()";
      if (token.equals(name) || token.equals(name + methodEnd)) {
        StringBuilder buf = new StringBuilder();
        buf.append("Method ").append(YELLOW).append(method.getName()).append(RESET);
        buf.append(" returns ").append(AQUA).append(method.getReturnType().getName());
        buf.append(RESET).append('\n');
        Class[] types = method.getParameterTypes();
        buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
        for (Class type : types) buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
        buf.deleteCharAt(buf.length() - 1);
        caller.sendPrintMessage(buf, source);
      }
      int args = params.length;
      if (name.startsWith("get") && args == 0 && name.length() > 3) {
        String propertyName = getPropertyName(name);
        if (propertyName != null && propertyName.equals(token)) {
          caller.sendPrintMessage(
              String.format(
                  "Getter %s returns %s",
                  YELLOW + name + "()" + RESET, AQUA + method.getReturnType().getName() + RESET),
              source);
        }
      }
    }
    if (Enum.class.isAssignableFrom(theClass)) {
      Enum[] enumValues = getEnumValues(theClass);
      if (enumValues != null)
        for (Enum anEnum : enumValues) {
          String name = anEnum.name();
          if (name.equals(token)) {
            caller.sendPrintMessage(
                String.format(
                    "Enum value %s: %s", YELLOW + name + RESET, AQUA + theClass.getName() + RESET),
                source);
          }
        }
    }
    if (classHook) {
      MetaProperty property = InvokerHelper.getMetaClass(Class.class).getMetaProperty(token);
      if (property != null) {
        caller.sendPrintMessage(
            String.format(
                "Meta property %s: %s",
                YELLOW + token + RESET, AQUA + property.getType().getName() + RESET),
            source);
      }
      for (MetaMethod metaMethod : InvokerHelper.getMetaClass(Class.class).getMetaMethods()) {
        String name = metaMethod.getName();
        String methodEnd = "(";
        Class<?>[] params = metaMethod.getNativeParameterTypes();
        if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
        else if (params.length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Method ").append(YELLOW).append(metaMethod.getName()).append(RESET);
          buf.append(" returns ").append(AQUA).append(metaMethod.getReturnType().getName());
          buf.append(RESET).append('\n');
          Class[] types = metaMethod.getNativeParameterTypes();
          buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
          for (Class type : types)
            buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
      }
      for (Method method : Class.class.getMethods()) {
        String name = method.getName();
        String methodEnd = "(";
        Class<?>[] params = method.getParameterTypes();
        if (params.length == 1 && Closure.class.isAssignableFrom(params[0])) methodEnd = "{";
        else if (params.length == 0) methodEnd = "()";
        if (firstToken.equals(name) || firstToken.equals(name + methodEnd)) {
          StringBuilder buf = new StringBuilder();
          buf.append("Method ").append(YELLOW).append(method.getName()).append(RESET);
          buf.append(" returns ").append(AQUA).append(method.getReturnType().getName());
          buf.append(RESET).append('\n');
          Class[] types = method.getParameterTypes();
          buf.append("arguments: ").append(YELLOW).append(types.length).append(RESET).append('\n');
          for (Class type : types)
            buf.append(AQUA).append(type.getName()).append(RESET).append('\n');
          buf.deleteCharAt(buf.length() - 1);
          caller.sendPrintMessage(buf, source);
        }
      }
    }
  }
Beispiel #9
0
  public static void mixinClassesToMetaClass(MetaClass self, List<Class> categoryClasses) {
    final Class selfClass = self.getTheClass();

    if (self instanceof HandleMetaClass) {
      self = (MetaClass) ((HandleMetaClass) self).replaceDelegate();
    }

    if (!(self instanceof ExpandoMetaClass)) {
      if (self instanceof DelegatingMetaClass
          && ((DelegatingMetaClass) self).getAdaptee() instanceof ExpandoMetaClass) {
        self = ((DelegatingMetaClass) self).getAdaptee();
      } else {
        throw new GroovyRuntimeException("Can't mixin methods to meta class: " + self);
      }
    }

    ExpandoMetaClass mc = (ExpandoMetaClass) self;

    List<MetaMethod> arr = new ArrayList<MetaMethod>();
    for (Class categoryClass : categoryClasses) {

      final CachedClass cachedCategoryClass = ReflectionCache.getCachedClass(categoryClass);
      final MixinInMetaClass mixin = new MixinInMetaClass(mc, cachedCategoryClass);

      final MetaClass metaClass = GroovySystem.getMetaClassRegistry().getMetaClass(categoryClass);
      final List<MetaProperty> propList = metaClass.getProperties();
      for (MetaProperty prop : propList)
        if (self.getMetaProperty(prop.getName()) == null) {
          mc.registerBeanProperty(prop.getName(), new MixinInstanceMetaProperty(prop, mixin));
        }

      for (MetaProperty prop : cachedCategoryClass.getFields())
        if (self.getMetaProperty(prop.getName()) == null) {
          mc.registerBeanProperty(prop.getName(), new MixinInstanceMetaProperty(prop, mixin));
        }

      for (MetaMethod method : metaClass.getMethods()) {
        final int mod = method.getModifiers();

        if (!Modifier.isPublic(mod)) continue;

        if (method instanceof CachedMethod
            && ((CachedMethod) method).getCachedMethod().isSynthetic()) continue;

        if (Modifier.isStatic(mod)) {
          if (method instanceof CachedMethod) staticMethod(self, arr, (CachedMethod) method);
        } else if (method.getDeclaringClass().getTheClass() != Object.class
            || method.getName().equals("toString")) {
          //                    if (self.pickMethod(method.getName(),
          // method.getNativeParameterTypes()) == null) {
          final MixinInstanceMetaMethod metaMethod = new MixinInstanceMetaMethod(method, mixin);
          arr.add(metaMethod);
          //                    }
        }
      }
    }

    for (Object res : arr) {
      final MetaMethod metaMethod = (MetaMethod) res;
      if (metaMethod.getDeclaringClass().isAssignableFrom(selfClass))
        mc.registerInstanceMethod(metaMethod);
      else {
        mc.registerSubclassInstanceMethod(metaMethod);
      }
    }
  }
 public String getName() {
   return method.getName();
 }