{"action":"created","comment":{"url":"https://api.github.com/repos/apache/incubator-beam/pulls/comments/86282638","pull_request_review_id":6945723,"id":86282638,"diff_hunk":"@@ -474,203 +641,234 @@ private static void verifyUnsplittableMethods(ErrorReporter errors, DoFnSignatur\n         \"Must take %s as the first argument\",\n         formatType(processContextToken));\n \n-    List<DoFnSignature.Parameter> extraParameters = new ArrayList<>();\n-    Map<String, DoFnSignature.Parameter> stateParameters = new HashMap<>();\n-    Map<String, DoFnSignature.Parameter> timerParameters = new HashMap<>();\n-    TypeToken<?> trackerT = null;\n-\n-    TypeToken<?> expectedInputProviderT = inputProviderTypeOf(inputT);\n-    TypeToken<?> expectedOutputReceiverT = outputReceiverTypeOf(outputT);\n+    TypeDescriptor<?> trackerT = getTrackerType(fnClass, m);\n+    TypeDescriptor<?> expectedInputProviderT = inputProviderTypeOf(inputT);\n+    TypeDescriptor<?> expectedOutputReceiverT = outputReceiverTypeOf(outputT);\n     for (int i = 1; i < params.length; ++i) {\n-      TypeToken<?> paramT = fnClass.resolveType(params[i]);\n-      Class<?> rawType = paramT.getRawType();\n-      if (rawType.equals(BoundedWindow.class)) {\n-        errors.checkArgument(\n-            !extraParameters.contains(DoFnSignature.Parameter.boundedWindow()),\n-            \"Multiple %s parameters\",\n-            BoundedWindow.class.getSimpleName());\n-        extraParameters.add(DoFnSignature.Parameter.boundedWindow());\n-      } else if (rawType.equals(DoFn.InputProvider.class)) {\n-        errors.checkArgument(\n-            !extraParameters.contains(DoFnSignature.Parameter.inputProvider()),\n-            \"Multiple %s parameters\",\n-            DoFn.InputProvider.class.getSimpleName());\n-        errors.checkArgument(\n-            paramT.equals(expectedInputProviderT),\n-            \"Wrong type of %s parameter: %s, should be %s\",\n-            DoFn.InputProvider.class.getSimpleName(),\n-            formatType(paramT),\n-            formatType(expectedInputProviderT));\n-        extraParameters.add(DoFnSignature.Parameter.inputProvider());\n-      } else if (rawType.equals(DoFn.OutputReceiver.class)) {\n-        errors.checkArgument(\n-            !extraParameters.contains(DoFnSignature.Parameter.outputReceiver()),\n-            \"Multiple %s parameters\",\n-            DoFn.OutputReceiver.class.getSimpleName());\n-        errors.checkArgument(\n-            paramT.equals(expectedOutputReceiverT),\n-            \"Wrong type of %s parameter: %s, should be %s\",\n-            DoFn.OutputReceiver.class.getSimpleName(),\n-            formatType(paramT),\n-            formatType(expectedOutputReceiverT));\n-        extraParameters.add(DoFnSignature.Parameter.outputReceiver());\n-      } else if (Timer.class.equals(rawType)) {\n-        // m.getParameters() is not available until Java 8\n-        Annotation[] annotations = m.getParameterAnnotations()[i];\n-        String id = null;\n-        for (Annotation anno : annotations) {\n-          if (anno.annotationType().equals(DoFn.TimerId.class)) {\n-            id = ((DoFn.TimerId) anno).value();\n-            break;\n-          }\n-        }\n-        errors.checkArgument(\n-            id != null,\n-            \"%s parameter of type %s at index %s missing %s annotation\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            DoFn.TimerId.class.getSimpleName());\n \n-        errors.checkArgument(\n-            !timerParameters.containsKey(id),\n-            \"%s parameter of type %s at index %s duplicates %s(\\\"%s\\\") on other parameter\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            DoFn.TimerId.class.getSimpleName(),\n-            id);\n-\n-        TimerDeclaration timerDecl = timerDeclarations.get(id);\n-        errors.checkArgument(\n-            timerDecl != null,\n-            \"%s parameter of type %s at index %s references undeclared %s \\\"%s\\\"\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            TimerId.class.getSimpleName(),\n-            id);\n+      Parameter extraParam =\n+          analyzeExtraParameter(\n+              errors.forMethod(DoFn.ProcessElement.class, m),\n+              globalContext,\n+              methodContext,\n+              fnClass,\n+              ParameterDescription.of(\n+                  m,\n+                  i,\n+                  fnClass.resolveType(params[i]),\n+                  Arrays.asList(m.getParameterAnnotations()[i])),\n+              expectedInputProviderT,\n+              expectedOutputReceiverT);\n+\n+      methodContext.addParameter(extraParam);\n+    }\n \n-        errors.checkArgument(\n-            timerDecl.field().getDeclaringClass().equals(m.getDeclaringClass()),\n-            \"Method %s has %s parameter at index %s for timer %s\"\n-                + \" declared in a different class %s.\"\n-                + \" Timers may be referenced only in the lexical scope where they are declared.\",\n-            m,\n-            Timer.class.getSimpleName(),\n-            i,\n-            id,\n-            timerDecl.field().getDeclaringClass().getName());\n+    // A splittable DoFn can not have any other extra context parameters.\n+    if (methodContext.getExtraParameters().contains(DoFnSignature.Parameter.restrictionTracker())) {\n+      errors.checkArgument(\n+          methodContext.getExtraParameters().size() == 1,\n+          \"Splittable DoFn must not have any extra arguments apart from BoundedWindow, but has: %s\",\n+          trackerT,\n+          methodContext.getExtraParameters());\n+    }\n \n-        DoFnSignature.Parameter.TimerParameter timerParameter = Parameter.timerParameter(timerDecl);\n-        timerParameters.put(id, timerParameter);\n-        extraParameters.add(timerParameter);\n+    return DoFnSignature.ProcessElementMethod.create(\n+        m,\n+        methodContext.getExtraParameters(),\n+        trackerT,\n+        DoFn.ProcessContinuation.class.equals(m.getReturnType()));\n+  }\n \n-      } else if (RestrictionTracker.class.isAssignableFrom(rawType)) {\n-        errors.checkArgument(\n-            !extraParameters.contains(DoFnSignature.Parameter.restrictionTracker()),\n-            \"Multiple %s parameters\",\n-            RestrictionTracker.class.getSimpleName());\n-        extraParameters.add(DoFnSignature.Parameter.restrictionTracker());\n-        trackerT = paramT;\n-      } else if (State.class.isAssignableFrom(rawType)) {\n-        // m.getParameters() is not available until Java 8\n-        Annotation[] annotations = m.getParameterAnnotations()[i];\n-        String id = null;\n-        for (Annotation anno : annotations) {\n-          if (anno.annotationType().equals(DoFn.StateId.class)) {\n-            id = ((DoFn.StateId) anno).value();\n-            break;\n-          }\n-        }\n-        errors.checkArgument(\n-            id != null,\n-            \"%s parameter of type %s at index %s missing %s annotation\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            DoFn.StateId.class.getSimpleName());\n+  private static Parameter analyzeExtraParameter(\n+      ErrorReporter errors,\n+      GlobalAnalysisContext globalContext,\n+      MethodAnalysisContext methodContext,\n+      TypeDescriptor<? extends DoFn<?, ?>> fnClass,\n+      ParameterDescription param,\n+      TypeDescriptor<?> expectedInputProviderT,\n+      TypeDescriptor<?> expectedOutputReceiverT) {\n+    TypeDescriptor<?> paramT = param.getType();\n+    Class<?> rawType = paramT.getRawType();\n+    if (rawType.equals(BoundedWindow.class)) {\n+      errors.checkArgument(\n+          !methodContext.getExtraParameters().contains(Parameter.boundedWindow()),\n+          \"Multiple %s parameters\",\n+          BoundedWindow.class.getSimpleName());\n+      return Parameter.boundedWindow();\n+    } else if (rawType.equals(DoFn.InputProvider.class)) {\n+      errors.checkArgument(\n+          !methodContext.getExtraParameters().contains(Parameter.inputProvider()),\n+          \"Multiple %s parameters\",\n+          DoFn.InputProvider.class.getSimpleName());\n+      errors.checkArgument(\n+          paramT.equals(expectedInputProviderT),\n+          \"Wrong type of %s parameter: %s, should be %s\",\n+          DoFn.InputProvider.class.getSimpleName(),\n+          formatType(paramT),\n+          formatType(expectedInputProviderT));\n+      return Parameter.inputProvider();\n+    } else if (rawType.equals(DoFn.OutputReceiver.class)) {\n+      errors.checkArgument(\n+          !methodContext.getExtraParameters().contains(Parameter.outputReceiver()),\n+          \"Multiple %s parameters\",\n+          DoFn.OutputReceiver.class.getSimpleName());\n+      errors.checkArgument(\n+          paramT.equals(expectedOutputReceiverT),\n+          \"Wrong type of %s parameter: %s, should be %s\",\n+          DoFn.OutputReceiver.class.getSimpleName(),\n+          formatType(paramT),\n+          formatType(expectedOutputReceiverT));\n+      return Parameter.outputReceiver();\n+    } else if (Timer.class.equals(rawType)) {\n+      // m.getParameters() is not available until Java 8\n+      String id = getTimerId(param.getAnnotations());\n \n-        errors.checkArgument(\n-            !stateParameters.containsKey(id),\n-            \"%s parameter of type %s at index %s duplicates %s(\\\"%s\\\") on other parameter\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            DoFn.StateId.class.getSimpleName(),\n-            id);\n+      errors.checkArgument(\n+          id != null,\n+          \"parameter of type %s at index %s missing %s annotation\",\n+          param.getType(),\n+          param.getIndex(),\n+          TimerId.class.getSimpleName());\n \n-        // By static typing this is already a well-formed State subclass\n-        TypeDescriptor<? extends State> stateType =\n-            (TypeDescriptor<? extends State>)\n-                TypeDescriptor.of(fnClass.getType())\n-                    .resolveType(params[i]);\n+      errors.checkArgument(\n+          !methodContext.getTimerParameters().containsKey(id),\n+          \"parameter of type %s at index %s duplicates %s(\\\"%s\\\") on other parameter\",\n+          param.getType(),\n+          param.getIndex(),\n+          TimerId.class.getSimpleName(),\n+          id);\n \n-        StateDeclaration stateDecl = stateDeclarations.get(id);\n-        errors.checkArgument(\n-            stateDecl != null,\n-            \"%s parameter of type %s at index %s references undeclared %s \\\"%s\\\"\",\n-            fnClass.getRawType().getName(),\n-            params[i],\n-            i,\n-            DoFn.StateId.class.getSimpleName(),\n-            id);\n+      TimerDeclaration timerDecl = globalContext.getTimerDeclarations().get(id);\n+      errors.checkArgument(\n+          timerDecl != null,\n+          \"parameter of type %s at index %s references undeclared %s \\\"%s\\\"\",\n+          param.getType(),\n+          param.getIndex(),\n+          TimerId.class.getSimpleName(),\n+          id);\n \n-        errors.checkArgument(\n-            stateDecl.stateType().equals(stateType),\n-            \"%s parameter at index %s has type %s but is a reference to StateId %s of type %s\",\n-            fnClass.getRawType().getName(),\n-            i,\n-            params[i],\n-            id,\n-            stateDecl.stateType());\n+      errors.checkArgument(\n+          timerDecl.field().getDeclaringClass().equals(param.getMethod().getDeclaringClass()),\n+          \"parameter at index %s for timer %s\"\n+              + \" declared in a different class %s.\"\n+              + \" Timers may be referenced only in the lexical scope where they are declared.\",\n+          Timer.class.getSimpleName(),\n+          param.getIndex(),\n+          id,\n+          timerDecl.field().getDeclaringClass().getName());\n \n-        errors.checkArgument(\n-            stateDecl.field().getDeclaringClass().equals(m.getDeclaringClass()),\n-            \"Method %s has State parameter at index %s for state %s\"\n-                + \" declared in a different class %s.\"\n-                + \" State may be referenced only in the class where it is declared.\",\n-            m,\n-            i,\n-            id,\n-            stateDecl.field().getDeclaringClass().getName());\n-\n-        DoFnSignature.Parameter.StateParameter stateParameter = Parameter.stateParameter(stateDecl);\n-        stateParameters.put(id, stateParameter);\n-        extraParameters.add(stateParameter);\n-      } else {\n-        List<String> allowedParamTypes =\n-            Arrays.asList(\n-                formatType(new TypeToken<BoundedWindow>() {}),\n-                formatType(new TypeToken<RestrictionTracker<?>>() {}));\n-        errors.throwIllegalArgument(\n-            \"%s is not a valid context parameter. Should be one of %s\",\n-            formatType(paramT), allowedParamTypes);\n+      return Parameter.timerParameter(timerDecl);\n+\n+    } else if (RestrictionTracker.class.isAssignableFrom(rawType)) {\n+      errors.checkArgument(\n+          !methodContext.getExtraParameters().contains(Parameter.restrictionTracker()),\n+          \"Multiple %s parameters\",\n+          RestrictionTracker.class.getSimpleName());\n+      return Parameter.restrictionTracker();\n+    } else if (State.class.isAssignableFrom(rawType)) {\n+      // m.getParameters() is not available until Java 8\n+      String id = getStateId(param.getAnnotations());\n+      errors.checkArgument(\n+          id != null,\n+          \"parameter of type %s at index %s missing %s annotation\",\n+          param.getType(),\n+          param.getIndex(),\n+          DoFn.StateId.class.getSimpleName());\n+\n+      errors.checkArgument(\n+          !methodContext.getStateParameters().containsKey(id),\n+          \"%s parameter of type %s at index %s duplicates %s(\\\"%s\\\") on other parameter\",\n+          fnClass.getRawType().getName(),\n+          param.getType(),\n+          param.getIndex(),\n+          DoFn.StateId.class.getSimpleName(),\n+          id);\n+\n+      // By static typing this is already a well-formed State subclass\n+      TypeDescriptor<? extends State> stateType = (TypeDescriptor<? extends State>) param.getType();\n+\n+      StateDeclaration stateDecl = globalContext.getStateDeclarations().get(id);\n+      errors.checkArgument(\n+          stateDecl != null,\n+          \"parameter of type %s at index %s references undeclared %s \\\"%s\\\"\",\n+          param.getType(),\n+          param.getIndex(),\n+          DoFn.StateId.class.getSimpleName(),\n+          id);\n+\n+      errors.checkArgument(\n+          stateDecl.stateType().equals(stateType),\n+          \"parameter at index %s has type %s but is a reference to StateId %s of type %s\",\n+          param.getIndex(),\n+          param.getType(),\n+          id,\n+          stateDecl.stateType());\n+\n+      errors.checkArgument(\n+          stateDecl.field().getDeclaringClass().equals(param.getMethod().getDeclaringClass()),\n+          \"Method %s has State parameter at index %s for state %s\"\n+              + \" declared in a different class %s.\"\n+              + \" State may be referenced only in the class where it is declared.\",\n+          param.getMethod(),\n+          param.getIndex(),\n+          id,\n+          stateDecl.field().getDeclaringClass().getName());\n+\n+      return Parameter.stateParameter(stateDecl);\n+    } else {\n+      List<String> allowedParamTypes =\n+          Arrays.asList(\n+              formatType(new TypeDescriptor<BoundedWindow>() {}),\n+              formatType(new TypeDescriptor<RestrictionTracker<?>>() {}));\n+      errors.throwIllegalArgument(\n+          \"%s is not a valid context parameter. Should be one of %s\",","path":"sdks/java/core/src/main/java/org/apache/beam/sdk/transforms/reflect/DoFnSignatures.java","position":null,"original_position":808,"commit_id":"cf935e149702eed6fd84b52f175d833cb15bb406","original_commit_id":"8bd33b42adabe64bb5c3ade741c9ffa91b62df0b","user":{"login":"kennknowles","id":575353,"avatar_url":"https://avatars.githubusercontent.com/u/575353?v=3","gravatar_id":"","url":"https://api.github.com/users/kennknowles","html_url":"https://github.com/kennknowles","followers_url":"https://api.github.com/users/kennknowles/followers","following_url":"https://api.github.com/users/kennknowles/following{/other_user}","gists_url":"https://api.github.com/users/kennknowles/gists{/gist_id}","starred_url":"https://api.github.com/users/kennknowles/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kennknowles/subscriptions","organizations_url":"https://api.github.com/users/kennknowles/orgs","repos_url":"https://api.github.com/users/kennknowles/repos","events_url":"https://api.github.com/users/kennknowles/events{/privacy}","received_events_url":"https://api.github.com/users/kennknowles/received_events","type":"User","site_admin":false},"body":"Too early. Don't want to point at features that are in progress.","created_at":"2016-11-03T03:35:50Z","updated_at":"2016-11-03T03:41:39Z","html_url":"https://github.com/apache/incubator-beam/pull/1252#discussion_r86282638","pull_request_url":"https://api.github.com/repos/apache/incubator-beam/pulls/1252","_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/comments/86282638"},"html":{"href":"https://github.com/apache/incubator-beam/pull/1252#discussion_r86282638"},"pull_request":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/1252"}}},"pull_request":{"url":"https://api.github.com/repos/apache/incubator-beam/pulls/1252","id":91878281,"html_url":"https://github.com/apache/incubator-beam/pull/1252","diff_url":"https://github.com/apache/incubator-beam/pull/1252.diff","patch_url":"https://github.com/apache/incubator-beam/pull/1252.patch","issue_url":"https://api.github.com/repos/apache/incubator-beam/issues/1252","number":1252,"state":"open","locked":false,"title":"[BEAM-27] Factor out parameter analysis in DoFnSignatures; use it for @OnTimer methods","user":{"login":"kennknowles","id":575353,"avatar_url":"https://avatars.githubusercontent.com/u/575353?v=3","gravatar_id":"","url":"https://api.github.com/users/kennknowles","html_url":"https://github.com/kennknowles","followers_url":"https://api.github.com/users/kennknowles/followers","following_url":"https://api.github.com/users/kennknowles/following{/other_user}","gists_url":"https://api.github.com/users/kennknowles/gists{/gist_id}","starred_url":"https://api.github.com/users/kennknowles/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kennknowles/subscriptions","organizations_url":"https://api.github.com/users/kennknowles/orgs","repos_url":"https://api.github.com/users/kennknowles/repos","events_url":"https://api.github.com/users/kennknowles/events{/privacy}","received_events_url":"https://api.github.com/users/kennknowles/received_events","type":"User","site_admin":false},"body":"Be sure to do all of the following to help us incorporate your contribution\r\nquickly and easily:\r\n\r\n - [x] Make sure the PR title is formatted like:\r\n   `[BEAM-<Jira issue #>] Description of pull request`\r\n - [x] Make sure tests pass via `mvn clean verify`. (Even better, enable\r\n       Travis-CI on your fork and ensure the whole test matrix passes).\r\n - [x] Replace `<Jira issue #>` in the title with the actual Jira issue\r\n       number, if there is one.\r\n - [x] If this contribution is large, please file an Apache\r\n       [Individual Contributor License Agreement](https://www.apache.org/licenses/icla.txt).\r\n\r\n---\r\n\r\nR: @jkff \r\n\r\nThis might be easiest to review as individual commits, though the last one is the goal of this PR.\r\n\r\nThis PR is a prefix of ongoing work to invoke `@OnTimer` methods. I've peeled it off and fixed it up and added a smidge of testing. Since the code is entirely shared, just making sure the right path is taken provides moderate coverage. (a further refactor could make this clearer, by isolating the shared code into a class with its own unit tests, absolving its callers of proving they exercise the code path, but I thought this baby step was better)","created_at":"2016-11-01T22:42:36Z","updated_at":"2016-11-03T03:41:39Z","closed_at":null,"merged_at":null,"merge_commit_sha":"b896944e52c8fb48da72f6d73f1bdcd068667087","assignee":null,"assignees":[],"milestone":null,"commits_url":"https://api.github.com/repos/apache/incubator-beam/pulls/1252/commits","review_comments_url":"https://api.github.com/repos/apache/incubator-beam/pulls/1252/comments","review_comment_url":"https://api.github.com/repos/apache/incubator-beam/pulls/comments{/number}","comments_url":"https://api.github.com/repos/apache/incubator-beam/issues/1252/comments","statuses_url":"https://api.github.com/repos/apache/incubator-beam/statuses/cf935e149702eed6fd84b52f175d833cb15bb406","head":{"label":"kennknowles:DoFnSignatures","ref":"DoFnSignatures","sha":"cf935e149702eed6fd84b52f175d833cb15bb406","user":{"login":"kennknowles","id":575353,"avatar_url":"https://avatars.githubusercontent.com/u/575353?v=3","gravatar_id":"","url":"https://api.github.com/users/kennknowles","html_url":"https://github.com/kennknowles","followers_url":"https://api.github.com/users/kennknowles/followers","following_url":"https://api.github.com/users/kennknowles/following{/other_user}","gists_url":"https://api.github.com/users/kennknowles/gists{/gist_id}","starred_url":"https://api.github.com/users/kennknowles/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kennknowles/subscriptions","organizations_url":"https://api.github.com/users/kennknowles/orgs","repos_url":"https://api.github.com/users/kennknowles/repos","events_url":"https://api.github.com/users/kennknowles/events{/privacy}","received_events_url":"https://api.github.com/users/kennknowles/received_events","type":"User","site_admin":false},"repo":{"id":52646147,"name":"incubator-beam","full_name":"kennknowles/incubator-beam","owner":{"login":"kennknowles","id":575353,"avatar_url":"https://avatars.githubusercontent.com/u/575353?v=3","gravatar_id":"","url":"https://api.github.com/users/kennknowles","html_url":"https://github.com/kennknowles","followers_url":"https://api.github.com/users/kennknowles/followers","following_url":"https://api.github.com/users/kennknowles/following{/other_user}","gists_url":"https://api.github.com/users/kennknowles/gists{/gist_id}","starred_url":"https://api.github.com/users/kennknowles/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/kennknowles/subscriptions","organizations_url":"https://api.github.com/users/kennknowles/orgs","repos_url":"https://api.github.com/users/kennknowles/repos","events_url":"https://api.github.com/users/kennknowles/events{/privacy}","received_events_url":"https://api.github.com/users/kennknowles/received_events","type":"User","site_admin":false},"private":false,"html_url":"https://github.com/kennknowles/incubator-beam","description":"Mirror of Apache Beam (Incubating)","fork":true,"url":"https://api.github.com/repos/kennknowles/incubator-beam","forks_url":"https://api.github.com/repos/kennknowles/incubator-beam/forks","keys_url":"https://api.github.com/repos/kennknowles/incubator-beam/keys{/key_id}","collaborators_url":"https://api.github.com/repos/kennknowles/incubator-beam/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/kennknowles/incubator-beam/teams","hooks_url":"https://api.github.com/repos/kennknowles/incubator-beam/hooks","issue_events_url":"https://api.github.com/repos/kennknowles/incubator-beam/issues/events{/number}","events_url":"https://api.github.com/repos/kennknowles/incubator-beam/events","assignees_url":"https://api.github.com/repos/kennknowles/incubator-beam/assignees{/user}","branches_url":"https://api.github.com/repos/kennknowles/incubator-beam/branches{/branch}","tags_url":"https://api.github.com/repos/kennknowles/incubator-beam/tags","blobs_url":"https://api.github.com/repos/kennknowles/incubator-beam/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/kennknowles/incubator-beam/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/kennknowles/incubator-beam/git/refs{/sha}","trees_url":"https://api.github.com/repos/kennknowles/incubator-beam/git/trees{/sha}","statuses_url":"https://api.github.com/repos/kennknowles/incubator-beam/statuses/{sha}","languages_url":"https://api.github.com/repos/kennknowles/incubator-beam/languages","stargazers_url":"https://api.github.com/repos/kennknowles/incubator-beam/stargazers","contributors_url":"https://api.github.com/repos/kennknowles/incubator-beam/contributors","subscribers_url":"https://api.github.com/repos/kennknowles/incubator-beam/subscribers","subscription_url":"https://api.github.com/repos/kennknowles/incubator-beam/subscription","commits_url":"https://api.github.com/repos/kennknowles/incubator-beam/commits{/sha}","git_commits_url":"https://api.github.com/repos/kennknowles/incubator-beam/git/commits{/sha}","comments_url":"https://api.github.com/repos/kennknowles/incubator-beam/comments{/number}","issue_comment_url":"https://api.github.com/repos/kennknowles/incubator-beam/issues/comments{/number}","contents_url":"https://api.github.com/repos/kennknowles/incubator-beam/contents/{+path}","compare_url":"https://api.github.com/repos/kennknowles/incubator-beam/compare/{base}...{head}","merges_url":"https://api.github.com/repos/kennknowles/incubator-beam/merges","archive_url":"https://api.github.com/repos/kennknowles/incubator-beam/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/kennknowles/incubator-beam/downloads","issues_url":"https://api.github.com/repos/kennknowles/incubator-beam/issues{/number}","pulls_url":"https://api.github.com/repos/kennknowles/incubator-beam/pulls{/number}","milestones_url":"https://api.github.com/repos/kennknowles/incubator-beam/milestones{/number}","notifications_url":"https://api.github.com/repos/kennknowles/incubator-beam/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/kennknowles/incubator-beam/labels{/name}","releases_url":"https://api.github.com/repos/kennknowles/incubator-beam/releases{/id}","deployments_url":"https://api.github.com/repos/kennknowles/incubator-beam/deployments","created_at":"2016-02-27T02:37:09Z","updated_at":"2016-02-27T02:37:12Z","pushed_at":"2016-11-03T03:41:24Z","git_url":"git://github.com/kennknowles/incubator-beam.git","ssh_url":"git@github.com:kennknowles/incubator-beam.git","clone_url":"https://github.com/kennknowles/incubator-beam.git","svn_url":"https://github.com/kennknowles/incubator-beam","homepage":null,"size":21278,"stargazers_count":0,"watchers_count":0,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":0,"mirror_url":null,"open_issues_count":0,"forks":0,"open_issues":0,"watchers":0,"default_branch":"master"}},"base":{"label":"apache:master","ref":"master","sha":"7e0cfe5b9c2ac2d0aeaf833a5e3097ce25a40c03","user":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"repo":{"id":50904245,"name":"incubator-beam","full_name":"apache/incubator-beam","owner":{"login":"apache","id":47359,"avatar_url":"https://avatars.githubusercontent.com/u/47359?v=3","gravatar_id":"","url":"https://api.github.com/users/apache","html_url":"https://github.com/apache","followers_url":"https://api.github.com/users/apache/followers","following_url":"https://api.github.com/users/apache/following{/other_user}","gists_url":"https://api.github.com/users/apache/gists{/gist_id}","starred_url":"https://api.github.com/users/apache/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/apache/subscriptions","organizations_url":"https://api.github.com/users/apache/orgs","repos_url":"https://api.github.com/users/apache/repos","events_url":"https://api.github.com/users/apache/events{/privacy}","received_events_url":"https://api.github.com/users/apache/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/apache/incubator-beam","description":"Mirror of Apache Beam (Incubating)","fork":false,"url":"https://api.github.com/repos/apache/incubator-beam","forks_url":"https://api.github.com/repos/apache/incubator-beam/forks","keys_url":"https://api.github.com/repos/apache/incubator-beam/keys{/key_id}","collaborators_url":"https://api.github.com/repos/apache/incubator-beam/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/apache/incubator-beam/teams","hooks_url":"https://api.github.com/repos/apache/incubator-beam/hooks","issue_events_url":"https://api.github.com/repos/apache/incubator-beam/issues/events{/number}","events_url":"https://api.github.com/repos/apache/incubator-beam/events","assignees_url":"https://api.github.com/repos/apache/incubator-beam/assignees{/user}","branches_url":"https://api.github.com/repos/apache/incubator-beam/branches{/branch}","tags_url":"https://api.github.com/repos/apache/incubator-beam/tags","blobs_url":"https://api.github.com/repos/apache/incubator-beam/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/apache/incubator-beam/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/apache/incubator-beam/git/refs{/sha}","trees_url":"https://api.github.com/repos/apache/incubator-beam/git/trees{/sha}","statuses_url":"https://api.github.com/repos/apache/incubator-beam/statuses/{sha}","languages_url":"https://api.github.com/repos/apache/incubator-beam/languages","stargazers_url":"https://api.github.com/repos/apache/incubator-beam/stargazers","contributors_url":"https://api.github.com/repos/apache/incubator-beam/contributors","subscribers_url":"https://api.github.com/repos/apache/incubator-beam/subscribers","subscription_url":"https://api.github.com/repos/apache/incubator-beam/subscription","commits_url":"https://api.github.com/repos/apache/incubator-beam/commits{/sha}","git_commits_url":"https://api.github.com/repos/apache/incubator-beam/git/commits{/sha}","comments_url":"https://api.github.com/repos/apache/incubator-beam/comments{/number}","issue_comment_url":"https://api.github.com/repos/apache/incubator-beam/issues/comments{/number}","contents_url":"https://api.github.com/repos/apache/incubator-beam/contents/{+path}","compare_url":"https://api.github.com/repos/apache/incubator-beam/compare/{base}...{head}","merges_url":"https://api.github.com/repos/apache/incubator-beam/merges","archive_url":"https://api.github.com/repos/apache/incubator-beam/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/apache/incubator-beam/downloads","issues_url":"https://api.github.com/repos/apache/incubator-beam/issues{/number}","pulls_url":"https://api.github.com/repos/apache/incubator-beam/pulls{/number}","milestones_url":"https://api.github.com/repos/apache/incubator-beam/milestones{/number}","notifications_url":"https://api.github.com/repos/apache/incubator-beam/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/apache/incubator-beam/labels{/name}","releases_url":"https://api.github.com/repos/apache/incubator-beam/releases{/id}","deployments_url":"https://api.github.com/repos/apache/incubator-beam/deployments","created_at":"2016-02-02T08:00:06Z","updated_at":"2016-11-02T23:17:24Z","pushed_at":"2016-11-03T03:41:25Z","git_url":"git://github.com/apache/incubator-beam.git","ssh_url":"git@github.com:apache/incubator-beam.git","clone_url":"https://github.com/apache/incubator-beam.git","svn_url":"https://github.com/apache/incubator-beam","homepage":null,"size":33610,"stargazers_count":416,"watchers_count":416,"language":"Java","has_issues":false,"has_downloads":true,"has_wiki":false,"has_pages":false,"forks_count":256,"mirror_url":"git://git.apache.org/incubator-beam.git","open_issues_count":65,"forks":256,"open_issues":65,"watchers":416,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/1252"},"html":{"href":"https://github.com/apache/incubator-beam/pull/1252"},"issue":{"href":"https://api.github.com/repos/apache/incubator-beam/issues/1252"},"comments":{"href":"https://api.github.com/repos/apache/incubator-beam/issues/1252/comments"},"review_comments":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/1252/comments"},"review_comment":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/apache/incubator-beam/pulls/1252/commits"},"statuses":{"href":"https://api.github.com/repos/apache/incubator-beam/statuses/cf935e149702eed6fd84b52f175d833cb15bb406"}}}}
{"actor":{"display_login":"kennknowles"}}