@Nullable
  static SourceOperation createSource(PsiMethodCallExpression call, boolean supportUnknownSources) {
    PsiExpression[] args = call.getArgumentList().getExpressions();
    PsiType callType = call.getType();
    if(callType == null || PsiTypes.voidType().equals(callType)) return null;
    PsiMethod method = call.resolveMethod();
    if(method == null) return null;
    String name = method.getName();
    PsiClass aClass = method.getContainingClass();
    if(aClass == null) return null;
    String className = aClass.getQualifiedName();
    if(className == null) return null;
    if ((name.equals("range") || name.equals("rangeClosed")) && args.length == 2 && method.getModifierList().hasExplicitModifier(
      PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
      return new RangeSource(args[0], args[1], name.equals("rangeClosed"));
    }
    if (name.equals("of") && method.getModifierList().hasExplicitModifier(
      PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
      if (method.getParameterList().getParametersCount() != 1) return null;
      if (args.length == 1) {
        PsiType type = args[0].getType();
        PsiType componentType = null;
        if (type instanceof PsiArrayType) {
          componentType = ((PsiArrayType)type).getComponentType();
        }
        else if (InheritanceUtil.isInheritor(type, CommonClassNames.JAVA_LANG_ITERABLE)) {
          componentType = PsiUtil.substituteTypeParameter(type, CommonClassNames.JAVA_LANG_ITERABLE, 0, false);
        }
        PsiType elementType = StreamApiUtil.getStreamElementType(callType);
        if (componentType != null && elementType.isAssignableFrom(componentType)) {
          return new ForEachSource(args[0]);
        }
        if (type == null || !elementType.isAssignableFrom(type)) return null;
      }
      return new ExplicitSource(call);
    }
    if (name.equals("generate") && args.length == 1 && method.getModifierList().hasExplicitModifier(
      PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
      FunctionHelper fn = FunctionHelper.create(args[0], 0);
      return fn == null ? null : new GenerateSource(fn, null);
    }
    if (name.equals("iterate") && args.length == 2 && method.getModifierList().hasExplicitModifier(
      PsiModifier.STATIC) && InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_STREAM_BASE_STREAM)) {
      FunctionHelper fn = FunctionHelper.create(args[1], 1);
      return fn == null ? null : new IterateSource(args[0], fn);
    }
    if (name.equals("stream") && args.length == 0 &&
        InheritanceUtil.isInheritor(aClass, CommonClassNames.JAVA_UTIL_COLLECTION)) {
      PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(call.getMethodExpression());
      if (qualifier != null) {
        return new ForEachSource(qualifier);
      }
    }
    if (name.equals("stream") && args.length == 2 &&
        method.getModifierList().hasExplicitModifier(PsiModifier.STATIC) &&
        "java.util.stream.StreamSupport".equals(className) &&
        ExpressionUtils.isLiteral(PsiUtil.skipParenthesizedExprDown(args[1]), false)) {
      PsiMethodCallExpression spliteratorExpr =
        ObjectUtils.tryCast(PsiUtil.skipParenthesizedExprDown(args[0]), PsiMethodCallExpression.class);
      if (ITERABLE_SPLITERATOR.test(spliteratorExpr)) {
        PsiExpression qualifier = ExpressionUtils.getEffectiveQualifier(spliteratorExpr.getMethodExpression());
        if (qualifier != null) {
          return new ForEachSource(qualifier);
        }
      }
    }
    if (name.equals("stream") && args.length == 1 &&
        CommonClassNames.JAVA_UTIL_ARRAYS.equals(className)) {
      return new ForEachSource(args[0]);
    }
    if (name.equals("stream") &&
        args.length == 3 &&
        CommonClassNames.JAVA_UTIL_ARRAYS.equals(className) &&
        args[0].getType() != null &&
        PsiTypes.intType().equals(args[1].getType()) &&
        PsiTypes.intType().equals(args[2].getType())) {
      return new ArraySliceSource(args[0], args[1], args[2]);
    }
    if (supportUnknownSources) {
      PsiType type = StreamApiUtil.getStreamElementType(call.getType(), false);
      if (type != null) {
        return new StreamIteratorSource(call, type);
      }
    }
    return null;
  }
@Nullable
  static SourceOperation createSource(PsiMethodCallExpression call, boolean supportUnknownSources)