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);
  }
 //  --------------------------------------------------------
 //                       methods for this
 //  --------------------------------------------------------
 public static Object invokeMethodOnCurrentN(
     Class senderClass, GroovyObject receiver, String messageName, Object[] messageArguments)
     throws Throwable {
   Object result = null;
   boolean intercepting = receiver instanceof GroovyInterceptable;
   try {
     try {
       // if it's a pure interceptable object (even intercepting toString(), clone(), ...)
       if (intercepting) {
         result = receiver.invokeMethod(messageName, messageArguments);
       }
       // else if there's a statically typed method or a GDK method
       else {
         result =
             receiver
                 .getMetaClass()
                 .invokeMethod(senderClass, receiver, messageName, messageArguments, false, true);
       }
     } catch (MissingMethodException e) {
       if (e instanceof MissingMethodExecutionFailed) {
         throw (MissingMethodException) e.getCause();
       } else if (!intercepting
           && receiver.getClass() == e.getType()
           && e.getMethod().equals(messageName)) {
         // in case there's nothing else, invoke the object's own invokeMethod()
         result = receiver.invokeMethod(messageName, messageArguments);
       } else {
         throw e;
       }
     }
   } catch (GroovyRuntimeException gre) {
     throw unwrap(gre);
   }
   return result;
 }
示例#3
0
  public void build(final GroovyObject builder) {
    final String[] s = getStrings();
    final int numberOfValues = values.length;

    for (int i = 0, size = s.length; i < size; i++) {
      builder.getProperty("mkp");
      builder.invokeMethod("yield", new Object[] {s[i]});
      if (i < numberOfValues) {
        builder.getProperty("mkp");
        builder.invokeMethod("yield", new Object[] {values[i]});
      }
    }
  }
 public static Object getGroovyObjectField(
     Class senderClass, GroovyObject receiver, String messageName) throws Throwable {
   try {
     return receiver.getMetaClass().getAttribute(receiver, messageName);
   } catch (GroovyRuntimeException gre) {
     throw unwrap(gre);
   }
 }
 public static void setGroovyObjectProperty(
     Object messageArgument, Class senderClass, GroovyObject receiver, String messageName)
     throws Throwable {
   try {
     receiver.setProperty(messageName, messageArgument);
   } catch (GroovyRuntimeException gre) {
     throw unwrap(gre);
   }
 }
 public static void setGroovyObjectField(
     Object messageArgument, Class senderClass, GroovyObject receiver, String messageName)
     throws Throwable {
   try {
     receiver.getMetaClass().setAttribute(receiver, messageName, messageArgument);
   } catch (GroovyRuntimeException gre) {
     throw unwrap(gre);
   }
 }
 @Override
 public Object invokeMethod(String name, Object... arguments) throws MissingMethodException {
   try {
     return groovyObject.invokeMethod(name, arguments);
   } catch (InvokerInvocationException e) {
     if (e.getCause() instanceof RuntimeException) {
       throw (RuntimeException) e.getCause();
     }
     throw e;
   }
 }
 //  --------------------------------------------------------
 //                       methods for super
 //  --------------------------------------------------------
 public static Object invokeMethodOnSuperN(
     Class senderClass, GroovyObject receiver, String messageName, Object[] messageArguments)
     throws Throwable {
   MetaClass metaClass = receiver.getMetaClass();
   // ignore interception and missing method fallback
   Object result = null;
   try {
     result =
         metaClass.invokeMethod(senderClass, receiver, messageName, messageArguments, true, true);
   } catch (GroovyRuntimeException gre) {
     throw unwrap(gre);
   }
   return result;
 }
示例#9
0
  private void renderView(Map argMap, Object target, GroovyObject controller) {
    String viewName = argMap.get(ARGUMENT_VIEW).toString();
    Object modelObject = argMap.get(ARGUMENT_MODEL);

    String viewUri = GroovyPageUtils.getNoSuffixViewURI((GroovyObject) target, viewName);
    Map model;
    if (modelObject instanceof Map) {
      model = (Map) modelObject;
    } else if (target instanceof GroovyObject) {
      model = new BeanMap(target);
    } else {
      model = new HashMap();
    }

    controller.setProperty(
        ControllerDynamicMethods.MODEL_AND_VIEW_PROPERTY, new ModelAndView(viewUri, model));
  }
示例#10
0
 public static void setGroovyObjectPropertySafe(
     Object messageArgument, Class senderClass, GroovyObject receiver, String messageName)
     throws Throwable {
   if (receiver == null) return;
   receiver.setProperty(messageName, messageArgument);
 }
示例#11
0
 public static Object getGroovyObjectProperty(
     Class senderClass, GroovyObject receiver, String messageName) throws Throwable {
   return receiver.getProperty(messageName);
 }
示例#12
0
 @Override
 public void setProperty(String name, Object value) throws MissingPropertyException {
   groovyObject.setProperty(name, value);
 }
示例#13
0
 @Override
 public Object getProperty(String name) throws MissingPropertyException {
   return groovyObject.getProperty(name);
 }
  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);
        }
      }
    }
  }
    /**
     * Turn the template into a writable Closure When executed the closure evaluates all the code
     * embedded in the template and then writes a GString containing the fixed and variable items to
     * the writer passed as a parameter
     *
     * <p>For example:
     *
     * <p>'<%= "test" %> of expr and <% test = 1 %>${test} script.'
     *
     * <p>would compile into:
     *
     * <p>{ out -> out << "${"test"} of expr and "; test = 1 ; out << "${test}
     * script."}.asWritable()
     *
     * @param reader
     * @param parentLoader
     * @throws CompilationFailedException
     * @throws ClassNotFoundException
     * @throws IOException
     */
    GStringTemplate(final Reader reader, final ClassLoader parentLoader)
        throws CompilationFailedException, ClassNotFoundException, IOException {
      final StringBuilder templateExpressions =
          new StringBuilder(
              "package groovy.tmp.templates\n def getTemplate() { return { out -> out << \"\"\"");
      boolean writingString = true;

      while (true) {
        int c = reader.read();
        if (c == -1) break;
        if (c == '<') {
          c = reader.read();
          if (c == '%') {
            c = reader.read();
            if (c == '=') {
              parseExpression(reader, writingString, templateExpressions);
              writingString = true;
              continue;
            } else {
              parseSection(c, reader, writingString, templateExpressions);
              writingString = false;
              continue;
            }
          } else {
            appendCharacter('<', templateExpressions, writingString);
            writingString = true;
          }
        } else if (c == '"') {
          appendCharacter('\\', templateExpressions, writingString);
          writingString = true;
        } else if (c == '$') {
          appendCharacter('$', templateExpressions, writingString);
          writingString = true;
          c = reader.read();
          if (c == '{') {
            appendCharacter('{', templateExpressions, writingString);
            writingString = true;
            parseGSstring(reader, writingString, templateExpressions);
            writingString = true;
            continue;
          }
        }
        appendCharacter((char) c, templateExpressions, writingString);
        writingString = true;
      }

      if (writingString) {
        templateExpressions.append("\"\"\"");
      }

      templateExpressions.append("}}");

      final GroovyClassLoader loader =
          parentLoader instanceof GroovyClassLoader
              ? (GroovyClassLoader) parentLoader
              : ((GroovyClassLoader)
                  AccessController.doPrivileged(
                      new PrivilegedAction() {
                        public Object run() {
                          return new GroovyClassLoader(parentLoader);
                        }
                      }));
      final Class groovyClass;
      try {
        groovyClass =
            loader.parseClass(
                new GroovyCodeSource(
                    templateExpressions.toString(),
                    "GStringTemplateScript" + counter.incrementAndGet() + ".groovy",
                    "x"));
      } catch (Exception e) {
        throw new GroovyRuntimeException(
            "Failed to parse template script (your template may contain an error or be trying to use expressions not currently supported): "
                + e.getMessage());
      }

      try {
        final GroovyObject script = (GroovyObject) groovyClass.newInstance();

        this.template = (Closure) script.invokeMethod("getTemplate", null);
        // GROOVY-6521: must set strategy to DELEGATE_FIRST, otherwise writing
        // books = 'foo' in a template would store 'books' in the binding of the template script
        // itself ("script")
        // instead of storing it in the delegate, which is a Binding too
        this.template.setResolveStrategy(Closure.DELEGATE_FIRST);
      } catch (InstantiationException e) {
        throw new ClassNotFoundException(e.getMessage());
      } catch (IllegalAccessException e) {
        throw new ClassNotFoundException(e.getMessage());
      }
    }