private JSONArray enrichProperties(String operatorClass, JSONArray properties)
      throws JSONException {
    JSONArray result = new JSONArray();
    for (int i = 0; i < properties.length(); i++) {
      JSONObject propJ = properties.getJSONObject(i);
      String propName = WordUtils.capitalize(propJ.getString("name"));
      String getPrefix =
          (propJ.getString("type").equals("boolean")
                  || propJ.getString("type").equals("java.lang.Boolean"))
              ? "is"
              : "get";
      String setPrefix = "set";
      OperatorClassInfo oci =
          getOperatorClassWithGetterSetter(
              operatorClass, setPrefix + propName, getPrefix + propName);
      if (oci == null) {
        result.put(propJ);
        continue;
      }
      MethodInfo setterInfo = oci.setMethods.get(setPrefix + propName);
      MethodInfo getterInfo = oci.getMethods.get(getPrefix + propName);

      if ((getterInfo != null && getterInfo.omitFromUI)
          || (setterInfo != null && setterInfo.omitFromUI)) {
        continue;
      }
      if (setterInfo != null) {
        addTagsToProperties(setterInfo, propJ);
      } else if (getterInfo != null) {
        addTagsToProperties(getterInfo, propJ);
      }
      result.put(propJ);
    }
    return result;
  }
  private void addTagsToProperties(MethodInfo mi, JSONObject propJ) throws JSONException {
    // create description object. description tag enables the visual tools to display description of
    // keys/values
    // of a map property, items of a list, properties within a complex type.
    JSONObject descriptionObj = new JSONObject();
    if (mi.comment != null) {
      descriptionObj.put("$", mi.comment);
    }
    for (Map.Entry<String, String> descEntry : mi.descriptions.entrySet()) {
      descriptionObj.put(descEntry.getKey(), descEntry.getValue());
    }
    if (descriptionObj.length() > 0) {
      propJ.put("descriptions", descriptionObj);
    }

    // create useSchema object. useSchema tag is added to enable visual tools to be able to render a
    // text field
    // as a dropdown with choices populated from the schema attached to the port.
    JSONObject useSchemaObj = new JSONObject();
    for (Map.Entry<String, String> useSchemaEntry : mi.useSchemas.entrySet()) {
      useSchemaObj.put(useSchemaEntry.getKey(), useSchemaEntry.getValue());
    }
    if (useSchemaObj.length() > 0) {
      propJ.put("useSchema", useSchemaObj);
    }
  }
 public JSONObject describeClass(Class<?> clazz) throws Exception {
   JSONObject desc = new JSONObject();
   desc.put("name", clazz.getName());
   if (clazz.isEnum()) {
     @SuppressWarnings("unchecked")
     Class<Enum<?>> enumClass = (Class<Enum<?>>) clazz;
     ArrayList<String> enumNames = Lists.newArrayList();
     for (Enum<?> e : enumClass.getEnumConstants()) {
       enumNames.add(e.name());
     }
     desc.put("enum", enumNames);
   }
   UI_TYPE ui_type = UI_TYPE.getEnumFor(clazz);
   if (ui_type != null) {
     desc.put("uiType", ui_type.getName());
   }
   desc.put("properties", getClassProperties(clazz, 0));
   return desc;
 }
  private JSONObject setFieldAttributes(String clazz, CompactFieldNode field) throws JSONException {
    JSONObject port = new JSONObject();
    port.put("name", field.getName());

    TypeGraphVertex tgv = typeGraph.getTypeGraphVertex(clazz);
    putFieldDescription(field, port, tgv);

    List<CompactAnnotationNode> annotations = field.getVisibleAnnotations();
    CompactAnnotationNode firstAnnotation;
    if (annotations != null
        && !annotations.isEmpty()
        && (firstAnnotation = field.getVisibleAnnotations().get(0)) != null) {
      for (Map.Entry<String, Object> entry : firstAnnotation.getAnnotations().entrySet()) {
        port.put(entry.getKey(), entry.getValue());
      }
    }

    return port;
  }
 @SuppressWarnings("unchecked")
 public void addDefaultValue(String className, JSONObject oper) throws Exception {
   ObjectMapper defaultValueMapper = ObjectMapperFactory.getOperatorValueSerializer();
   Class<? extends Operator> clazz = (Class<? extends Operator>) classLoader.loadClass(className);
   if (clazz != null) {
     Operator operIns = clazz.newInstance();
     String s = defaultValueMapper.writeValueAsString(operIns);
     oper.put("defaultValue", new JSONObject(s).get(className));
   }
 }
  private void putFieldDescription(CompactFieldNode field, JSONObject port, TypeGraphVertex tgv)
      throws JSONException {
    OperatorClassInfo oci = classInfo.get(tgv.typeName);
    if (oci != null) {
      String fieldDesc = oci.fields.get(field.getName());
      if (fieldDesc != null) {
        port.put("description", fieldDesc);
        return;
      }
    }

    for (TypeGraphVertex ancestor : tgv.getAncestors()) {
      putFieldDescription(field, port, ancestor);
    }
  }
  /**
   * Enrich portClassHier with class/interface names that map to a list of parent
   * classes/interfaces. For any class encountered, find its parents too.<br>
   * Also find the port types which have assignable schema classes.
   *
   * @param oper Operator to work on
   * @param portClassHierarchy In-Out param that contains a mapping of class/interface to its
   *     parents
   * @param portTypesWithSchemaClasses Json that will contain all the ports which have any schema
   *     classes.
   */
  public void buildAdditionalPortInfo(
      JSONObject oper, JSONObject portClassHierarchy, JSONObject portTypesWithSchemaClasses) {
    try {
      JSONArray ports = oper.getJSONArray(OperatorDiscoverer.PORT_TYPE_INFO_KEY);
      for (int i = 0; i < ports.length(); i++) {
        JSONObject port = ports.getJSONObject(i);

        String portType = port.optString("type");
        if (portType == null) {
          // skipping if port type is null
          continue;
        }

        if (typeGraph.size() == 0) {
          buildTypeGraph();
        }

        try {
          // building port class hierarchy
          LinkedList<String> queue = Lists.newLinkedList();
          queue.add(portType);
          while (!queue.isEmpty()) {
            String currentType = queue.remove();
            if (portClassHierarchy.has(currentType)) {
              // already present in the json so we skip.
              continue;
            }
            List<String> immediateParents = typeGraph.getParents(currentType);
            if (immediateParents == null) {
              portClassHierarchy.put(currentType, Lists.<String>newArrayList());
              continue;
            }
            portClassHierarchy.put(currentType, immediateParents);
            queue.addAll(immediateParents);
          }
        } catch (JSONException e) {
          LOG.warn("building port type hierarchy {}", portType, e);
        }

        // finding port types with schema classes
        if (portTypesWithSchemaClasses.has(portType)) {
          // already present in the json so skipping
          continue;
        }
        if (portType.equals("byte")
            || portType.equals("short")
            || portType.equals("char")
            || portType.equals("int")
            || portType.equals("long")
            || portType.equals("float")
            || portType.equals("double")
            || portType.equals("java.lang.String")
            || portType.equals("java.lang.Object")) {
          // ignoring primitives, strings and object types as this information is needed only for
          // complex types.
          continue;
        }
        if (port.has("typeArgs")) {
          // ignoring any type with generics
          continue;
        }
        boolean hasSchemaClasses = false;
        List<String> instantiableDescendants = typeGraph.getInstantiableDescendants(portType);
        if (instantiableDescendants != null) {
          for (String descendant : instantiableDescendants) {
            try {
              if (typeGraph.isInstantiableBean(descendant)) {
                hasSchemaClasses = true;
                break;
              }
            } catch (JSONException ex) {
              LOG.warn("checking descendant is instantiable {}", descendant);
            }
          }
        }
        portTypesWithSchemaClasses.put(portType, hasSchemaClasses);
      }
    } catch (JSONException e) {
      // should not reach this
      LOG.error("JSON Exception {}", e);
      throw new RuntimeException(e);
    }
  }
  private JSONArray getClassProperties(Class<?> clazz, int level) throws IntrospectionException {
    JSONArray arr = new JSONArray();
    TypeDiscoverer td = new TypeDiscoverer();
    try {
      for (PropertyDescriptor pd : Introspector.getBeanInfo(clazz).getPropertyDescriptors()) {
        Method readMethod = pd.getReadMethod();
        if (readMethod != null) {
          if (readMethod.getDeclaringClass() == java.lang.Enum.class) {
            // skip getDeclaringClass
            continue;
          } else if ("class".equals(pd.getName())) {
            // skip getClass
            continue;
          }
        } else {
          // yields com.datatorrent.api.Context on JDK6 and
          // com.datatorrent.api.Context.OperatorContext with JDK7
          if ("up".equals(pd.getName())
              && com.datatorrent.api.Context.class.isAssignableFrom(pd.getPropertyType())) {
            continue;
          }
        }
        // LOG.info("name: " + pd.getName() + " type: " + pd.getPropertyType());

        Class<?> propertyType = pd.getPropertyType();
        if (propertyType != null) {
          JSONObject propertyObj = new JSONObject();
          propertyObj.put("name", pd.getName());
          propertyObj.put("canGet", readMethod != null);
          propertyObj.put("canSet", pd.getWriteMethod() != null);
          if (readMethod != null) {
            for (Class<?> c = clazz; c != null; c = c.getSuperclass()) {
              OperatorClassInfo oci = classInfo.get(c.getName());
              if (oci != null) {
                MethodInfo getMethodInfo = oci.getMethods.get(readMethod.getName());
                if (getMethodInfo != null) {
                  addTagsToProperties(getMethodInfo, propertyObj);
                  break;
                }
              }
            }
            // type can be a type symbol or parameterized type
            td.setTypeArguments(clazz, readMethod.getGenericReturnType(), propertyObj);
          } else {
            if (pd.getWriteMethod() != null) {
              td.setTypeArguments(
                  clazz, pd.getWriteMethod().getGenericParameterTypes()[0], propertyObj);
            }
          }
          // if (!propertyType.isPrimitive() && !propertyType.isEnum() && !propertyType.isArray() &&
          // !propertyType.getName().startsWith("java.lang") && level < MAX_PROPERTY_LEVELS) {
          //  propertyObj.put("properties", getClassProperties(propertyType, level + 1));
          // }
          arr.put(propertyObj);
        }
      }
    } catch (JSONException ex) {
      throw new RuntimeException(ex);
    }
    return arr;
  }
  public JSONObject describeOperator(String clazz) throws Exception {
    TypeGraphVertex tgv = typeGraph.getTypeGraphVertex(clazz);
    if (tgv.isInstantiable()) {
      JSONObject response = new JSONObject();
      JSONArray inputPorts = new JSONArray();
      JSONArray outputPorts = new JSONArray();
      // Get properties from ASM

      JSONObject operatorDescriptor = describeClassByASM(clazz);
      JSONArray properties = operatorDescriptor.getJSONArray("properties");

      properties = enrichProperties(clazz, properties);

      JSONArray portTypeInfo = operatorDescriptor.getJSONArray("portTypeInfo");

      List<CompactFieldNode> inputPortfields = typeGraph.getAllInputPorts(clazz);
      List<CompactFieldNode> outputPortfields = typeGraph.getAllOutputPorts(clazz);

      try {
        for (CompactFieldNode field : inputPortfields) {
          JSONObject inputPort = setFieldAttributes(clazz, field);
          if (!inputPort.has("optional")) {
            inputPort.put(
                "optional",
                false); // input port that is not annotated is default to be not optional
          }
          if (!inputPort.has(SCHEMA_REQUIRED_KEY)) {
            inputPort.put(SCHEMA_REQUIRED_KEY, false);
          }
          inputPorts.put(inputPort);
        }

        for (CompactFieldNode field : outputPortfields) {
          JSONObject outputPort = setFieldAttributes(clazz, field);

          if (!outputPort.has("optional")) {
            outputPort.put(
                "optional", true); // output port that is not annotated is default to be optional
          }
          if (!outputPort.has("error")) {
            outputPort.put("error", false);
          }
          if (!outputPort.has(SCHEMA_REQUIRED_KEY)) {
            outputPort.put(SCHEMA_REQUIRED_KEY, false);
          }
          outputPorts.put(outputPort);
        }

        response.put("name", clazz);
        response.put("properties", properties);
        response.put(PORT_TYPE_INFO_KEY, portTypeInfo);
        response.put("inputPorts", inputPorts);
        response.put("outputPorts", outputPorts);

        OperatorClassInfo oci = classInfo.get(clazz);

        if (oci != null) {
          if (oci.comment != null) {
            String[] descriptions;
            // first look for a <p> tag
            String keptPrefix = "<p>";
            descriptions = oci.comment.split("<p>", 2);
            if (descriptions.length == 0) {
              keptPrefix = "";
              // if no <p> tag, then look for a blank line
              descriptions = oci.comment.split("\n\n", 2);
            }
            if (descriptions.length > 0) {
              response.put("shortDesc", descriptions[0]);
            }
            if (descriptions.length > 1) {
              response.put("longDesc", keptPrefix + descriptions[1]);
            }
          }
          response.put("category", oci.tags.get("@category"));
          String displayName = oci.tags.get("@displayName");
          if (displayName == null) {
            displayName = decamelizeClassName(ClassUtils.getShortClassName(clazz));
          }
          response.put("displayName", displayName);
          String tags = oci.tags.get("@tags");
          if (tags != null) {
            JSONArray tagArray = new JSONArray();
            for (String tag : StringUtils.split(tags, ',')) {
              tagArray.put(tag.trim().toLowerCase());
            }
            response.put("tags", tagArray);
          }
          String doclink = oci.tags.get("@doclink");
          if (doclink != null) {
            response.put("doclink", doclink + "?" + getDocName(clazz));
          } else if (clazz.startsWith("com.datatorrent.lib.")
              || clazz.startsWith("com.datatorrent.contrib.")) {
            response.put("doclink", DT_OPERATOR_DOCLINK_PREFIX + "?" + getDocName(clazz));
          }
        }
      } catch (JSONException ex) {
        throw new RuntimeException(ex);
      }
      return response;
    } else {
      throw new UnsupportedOperationException();
    }
  }