ErrorReporter forMethod(Class<? extends Annotation> annotation, Method method) { return new ErrorReporter( this, String.format( "@%s %s", annotation.getSimpleName(), (method == null) ? "(absent)" : format(method))); }
TypeDescriptor<?> outputT, FnAnalysisContext fnContext) { errors.checkArgument(void.class.equals(m.getReturnType()), "Must return void"); ErrorReporter onWindowExpirationErrors = errors.forMethod(DoFn.OnWindowExpiration.class, m); for (int i = 0; i < params.length; ++i) { Parameter parameter =
errors.throwIllegalArgument( "Duplicate %s \"%s\", used on both of [%s] and [%s]", DoFn.StateId.class.getSimpleName(), errors.throwIllegalArgument( "%s annotation on non-%s field [%s] that has class %s", DoFn.StateId.class.getSimpleName(), errors.throwIllegalArgument( "Non-final field %s annotated with %s. State declarations must be final.", field.toString(), DoFn.StateId.class.getSimpleName());
ErrorReporter errors = new ErrorReporter(null, fnClass.getName()); errors.checkArgument(DoFn.class.isAssignableFrom(fnClass), "Must be subtype of DoFn"); signatureBuilder.setFnClass(fnClass); errors.checkNotNull(inputT, "Unable to determine input type"); for (Method onTimerMethod : onTimerMethods) { String id = onTimerMethod.getAnnotation(DoFn.OnTimer.class).value(); errors.checkArgument( fnContext.getTimerDeclarations().containsKey(id), "Callback %s is for undeclared timer %s", errors.checkArgument( timerDecl.field().getDeclaringClass().equals(onTimerMethod.getDeclaringClass()), "Callback %s is for timer %s declared in a different class %s." errors.checkArgument( onTimerMethodMap.containsKey(decl.id()), "No callback registered via %s for timer %s", errors.forMethod(DoFn.ProcessElement.class, processElementMethod); DoFnSignature.ProcessElementMethod processElement = analyzeProcessElementMethod( ErrorReporter startBundleErrors = errors.forMethod(DoFn.StartBundle.class, startBundleMethod); signatureBuilder.setStartBundle( analyzeStartBundleMethod(startBundleErrors, fnT, startBundleMethod, inputT, outputT)); errors.forMethod(DoFn.FinishBundle.class, finishBundleMethod);
ErrorReporter paramErrors = methodErrors.forParameter(param); methodErrors.checkArgument( paramT.equals(inputT), "@Element argument must have type %s", inputT); return Parameter.elementParameter(paramT); methodErrors.checkArgument( rawType.equals(Instant.class), "@Timestamp argument must have type org.joda.time.Instant."); return Parameter.paneInfoParameter(); } else if (rawType.equals(DoFn.ProcessContext.class)) { paramErrors.checkArgument( paramT.equals(expectedProcessContextT), "ProcessContext argument must have type %s", return Parameter.processContext(); } else if (rawType.equals(DoFn.OnTimerContext.class)) { paramErrors.checkArgument( paramT.equals(expectedOnTimerContextT), "OnTimerContext argument must have type %s", return Parameter.onTimerContext(); } else if (BoundedWindow.class.isAssignableFrom(rawType)) { methodErrors.checkArgument( !methodContext.hasWindowParameter(), "Multiple %s parameters", if (!schemaRowReceiver) {
errors.forMethod(DoFn.ProcessElement.class, processElement.targetMethod()); errors.forMethod(DoFn.GetInitialRestriction.class, getInitialRestriction.targetMethod()); TypeDescriptor<?> restrictionT = getInitialRestriction.restrictionT(); getInitialRestrictionErrors.checkArgument( restrictionT.equals(newTracker.restrictionT()), "Uses restriction type %s, but @%s method %s uses restriction type %s", processElementErrors.throwIllegalArgument( "Splittable, but does not define the following required methods: %s", missingRequiredMethods); errors.forMethod(DoFn.GetInitialRestriction.class, getInitialRestriction.targetMethod()); TypeDescriptor<?> restrictionT = getInitialRestriction.restrictionT(); processElementErrors.checkArgument( processElement.trackerT().getRawType().equals(RestrictionTracker.class), "Has tracker type %s, but the DoFn's tracker type must be of type RestrictionTracker.", getInitialRestrictionErrors.checkArgument( getRestrictionCoder.coderT().isSubtypeOf(coderTypeOf(restrictionT)), "Uses restriction type %s, but @%s method %s returns %s " getInitialRestrictionErrors.checkArgument( splitRestriction.restrictionT().equals(restrictionT), "Uses restriction type %s, but @%s method %s uses restriction type %s",
if (supertype.getRawType().isAnnotationPresent(DoFn.BoundedPerElement.class) || supertype.getRawType().isAnnotationPresent(DoFn.UnboundedPerElement.class)) { errors.checkArgument( isBounded == null, "Both @%s and @%s specified", errors.checkArgument( isBounded == null, "Non-splittable, but annotated as @"
TypeDescriptor<?> outputT, FnAnalysisContext fnContext) { errors.checkArgument( void.class.equals(m.getReturnType()) || DoFn.ProcessContinuation.class.equals(m.getReturnType()), errors.forMethod(DoFn.ProcessElement.class, m), fnContext, methodContext,
TypeDescriptor<?> outputT, FnAnalysisContext fnContext) { errors.checkArgument(void.class.equals(m.getReturnType()), "Must return void"); ErrorReporter onTimerErrors = errors.forMethod(DoFn.OnTimer.class, m); for (int i = 0; i < params.length; ++i) { Parameter parameter =
@VisibleForTesting static DoFnSignature.SplitRestrictionMethod analyzeSplitRestrictionMethod( ErrorReporter errors, TypeDescriptor<? extends DoFn> fnT, Method m, TypeDescriptor<?> inputT) { // Method is of the form: // @SplitRestriction // void splitRestriction(InputT element, RestrictionT restriction); errors.checkArgument(void.class.equals(m.getReturnType()), "Must return void"); Type[] params = m.getGenericParameterTypes(); errors.checkArgument(params.length == 3, "Must have exactly 3 arguments"); errors.checkArgument( fnT.resolveType(params[0]).equals(inputT), "First argument must be the element type %s", formatType(inputT)); TypeDescriptor<?> restrictionT = fnT.resolveType(params[1]); TypeDescriptor<?> receiverT = fnT.resolveType(params[2]); TypeDescriptor<?> expectedReceiverT = outputReceiverTypeOf(restrictionT); errors.checkArgument( receiverT.equals(expectedReceiverT), "Third argument must be %s, but is %s", formatType(expectedReceiverT), formatType(receiverT)); return DoFnSignature.SplitRestrictionMethod.create(m, restrictionT); }
private static Map<String, DoFnSignature.FieldAccessDeclaration> analyzeFieldAccessDeclaration( ErrorReporter errors, Class<?> fnClazz) { Map<String, FieldAccessDeclaration> fieldAccessDeclarations = new HashMap<>(); for (Field field : declaredFieldsWithAnnotation(DoFn.FieldAccess.class, fnClazz, DoFn.class)) { field.setAccessible(true); DoFn.FieldAccess fieldAccessAnnotation = field.getAnnotation(DoFn.FieldAccess.class); if (!Modifier.isFinal(field.getModifiers())) { errors.throwIllegalArgument( "Non-final field %s annotated with %s. Field access declarations must be final.", field.toString(), DoFn.FieldAccess.class.getSimpleName()); continue; } Class<?> fieldAccessRawType = field.getType(); if (!fieldAccessRawType.equals(FieldAccessDescriptor.class)) { errors.throwIllegalArgument( "Field %s annotated with %s, but the value was not of type %s", field.toString(), DoFn.FieldAccess.class.getSimpleName(), FieldAccessDescriptor.class.getSimpleName()); } fieldAccessDeclarations.put( fieldAccessAnnotation.value(), FieldAccessDeclaration.create(fieldAccessAnnotation.value(), field)); } return fieldAccessDeclarations; }
/** * Returns successfully if the field is valid, otherwise throws an exception via its {@link * ErrorReporter} parameter describing validation failures for the timer declaration. */ private static void validateTimerField( ErrorReporter errors, Map<String, TimerDeclaration> declarations, String id, Field field) { if (declarations.containsKey(id)) { errors.throwIllegalArgument( "Duplicate %s \"%s\", used on both of [%s] and [%s]", DoFn.TimerId.class.getSimpleName(), id, field.toString(), declarations.get(id).field().toString()); } Class<?> timerSpecRawType = field.getType(); if (!(timerSpecRawType.equals(TimerSpec.class))) { errors.throwIllegalArgument( "%s annotation on non-%s field [%s]", DoFn.TimerId.class.getSimpleName(), TimerSpec.class.getSimpleName(), field.toString()); } if (!Modifier.isFinal(field.getModifiers())) { errors.throwIllegalArgument( "Non-final field %s annotated with %s. Timer declarations must be final.", field.toString(), DoFn.TimerId.class.getSimpleName()); } }
@Nullable private static Method findAnnotatedMethod( ErrorReporter errors, Class<? extends Annotation> anno, Class<?> fnClazz, boolean required) { Collection<Method> matches = declaredMethodsWithAnnotation(anno, fnClazz, DoFn.class); if (matches.isEmpty()) { errors.checkArgument(!required, "No method annotated with @%s found", anno.getSimpleName()); return null; } // If we have at least one match, then either it should be the only match // or it should be an extension of the other matches (which came from parent // classes). Method first = matches.iterator().next(); for (Method other : matches) { errors.checkArgument( first.getName().equals(other.getName()) && Arrays.equals(first.getParameterTypes(), other.getParameterTypes()), "Found multiple methods annotated with @%s. [%s] and [%s]", anno.getSimpleName(), format(first), format(other)); } ErrorReporter methodErrors = errors.forMethod(anno, first); // We need to be able to call it. We require it is public. methodErrors.checkArgument((first.getModifiers() & Modifier.PUBLIC) != 0, "Must be public"); // And make sure its not static. methodErrors.checkArgument((first.getModifiers() & Modifier.STATIC) == 0, "Must not be static"); return first; }
/** * Verifies that a non-splittable {@link DoFn} does not declare any methods that only make sense * for splittable {@link DoFn}: {@link DoFn.GetInitialRestriction}, {@link DoFn.SplitRestriction}, * {@link DoFn.NewTracker}, {@link DoFn.GetRestrictionCoder}. */ private static void verifyUnsplittableMethods(ErrorReporter errors, DoFnSignature signature) { List<String> forbiddenMethods = new ArrayList<>(); if (signature.getInitialRestriction() != null) { forbiddenMethods.add("@" + DoFn.GetInitialRestriction.class.getSimpleName()); } if (signature.splitRestriction() != null) { forbiddenMethods.add("@" + DoFn.SplitRestriction.class.getSimpleName()); } if (signature.newTracker() != null) { forbiddenMethods.add("@" + DoFn.NewTracker.class.getSimpleName()); } if (signature.getRestrictionCoder() != null) { forbiddenMethods.add("@" + DoFn.GetRestrictionCoder.class.getSimpleName()); } errors.checkArgument( forbiddenMethods.isEmpty(), "Non-splittable, but defines methods: %s", forbiddenMethods); }
@VisibleForTesting static DoFnSignature.BundleMethod analyzeFinishBundleMethod( ErrorReporter errors, TypeDescriptor<? extends DoFn<?, ?>> fnT, Method m, TypeDescriptor<?> inputT, TypeDescriptor<?> outputT) { errors.checkArgument(void.class.equals(m.getReturnType()), "Must return void"); TypeDescriptor<?> expectedContextT = doFnFinishBundleContextTypeOf(inputT, outputT); Type[] params = m.getGenericParameterTypes(); errors.checkArgument( params.length == 0 || (params.length == 1 && fnT.resolveType(params[0]).equals(expectedContextT)), "Must take a single argument of type %s", formatType(expectedContextT)); return DoFnSignature.BundleMethod.create(m); }
@VisibleForTesting static DoFnSignature.NewTrackerMethod analyzeNewTrackerMethod( ErrorReporter errors, TypeDescriptor<? extends DoFn> fnT, Method m) { // Method is of the form: // @NewTracker // TrackerT newTracker(RestrictionT restriction); Type[] params = m.getGenericParameterTypes(); errors.checkArgument(params.length == 1, "Must have a single argument"); TypeDescriptor<?> restrictionT = fnT.resolveType(params[0]); TypeDescriptor<?> trackerT = fnT.resolveType(m.getGenericReturnType()); TypeDescriptor<?> expectedTrackerT = restrictionTrackerTypeOf(restrictionT); errors.checkArgument( trackerT.isSubtypeOf(expectedTrackerT), "Returns %s, but must return a subtype of %s", formatType(trackerT), formatType(expectedTrackerT)); return DoFnSignature.NewTrackerMethod.create(m, restrictionT, trackerT); }
@VisibleForTesting static DoFnSignature.BundleMethod analyzeStartBundleMethod( ErrorReporter errors, TypeDescriptor<? extends DoFn<?, ?>> fnT, Method m, TypeDescriptor<?> inputT, TypeDescriptor<?> outputT) { errors.checkArgument(void.class.equals(m.getReturnType()), "Must return void"); TypeDescriptor<?> expectedContextT = doFnStartBundleContextTypeOf(inputT, outputT); Type[] params = m.getGenericParameterTypes(); errors.checkArgument( params.length == 0 || (params.length == 1 && fnT.resolveType(params[0]).equals(expectedContextT)), "Must take a single argument of type %s", formatType(expectedContextT)); return DoFnSignature.BundleMethod.create(m); }
@VisibleForTesting static DoFnSignature.GetInitialRestrictionMethod analyzeGetInitialRestrictionMethod( ErrorReporter errors, TypeDescriptor<? extends DoFn> fnT, Method m, TypeDescriptor<?> inputT) { // Method is of the form: // @GetInitialRestriction // RestrictionT getInitialRestriction(InputT element); Type[] params = m.getGenericParameterTypes(); errors.checkArgument( params.length == 1 && fnT.resolveType(params[0]).equals(inputT), "Must take a single argument of type %s", formatType(inputT)); return DoFnSignature.GetInitialRestrictionMethod.create( m, fnT.resolveType(m.getGenericReturnType())); }
/** An error reporter. */ static DoFnSignatures.ErrorReporter errors() { return new DoFnSignatures.ErrorReporter(null, "[test]"); }
ErrorReporter forParameter(ParameterDescription param) { return new ErrorReporter( this, String.format( "parameter of type %s at index %s", formatType(param.getType()), param.getIndex())); }