diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java index 051db7f4..da700d77 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/AnnotatedControllerConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -125,7 +125,7 @@ public class AnnotatedControllerConfigurer private HandlerMethodArgumentResolverComposite argumentResolvers; @Nullable - private HandlerMethodValidationHelper validationHelper; + private ValidationHelper validationHelper; /** @@ -176,8 +176,7 @@ public class AnnotatedControllerConfigurer this.argumentResolvers = initArgumentResolvers(); if (beanValidationPresent) { - this.validationHelper = - HandlerMethodValidationHelper.createIfValidatorAvailable(obtainApplicationContext()); + this.validationHelper = ValidationHelper.createIfValidatorPresent(obtainApplicationContext()); } } @@ -228,7 +227,8 @@ public class AnnotatedControllerConfigurer findHandlerMethods().forEach((info) -> { DataFetcher dataFetcher; if (!info.isBatchMapping()) { - dataFetcher = new SchemaMappingDataFetcher(info, this.argumentResolvers, this.validationHelper, this.executor); + dataFetcher = new SchemaMappingDataFetcher( + info, this.argumentResolvers, this.validationHelper, this.executor); } else { String dataLoaderKey = registerBatchLoader(info); @@ -492,7 +492,7 @@ public class AnnotatedControllerConfigurer private final HandlerMethodArgumentResolverComposite argumentResolvers; @Nullable - private final HandlerMethodValidationHelper validatorHelper; + private final Consumer methodValidationHelper; @Nullable private final Executor executor; @@ -501,12 +501,12 @@ public class AnnotatedControllerConfigurer SchemaMappingDataFetcher( MappingInfo info, HandlerMethodArgumentResolverComposite resolvers, - @Nullable HandlerMethodValidationHelper validatorHelper, - @Nullable Executor executor) { + @Nullable ValidationHelper validationHelper, @Nullable Executor executor) { this.info = info; this.argumentResolvers = resolvers; - this.validatorHelper = validatorHelper; + this.methodValidationHelper = (validationHelper != null ? + validationHelper.getValidationHelperFor(info.getHandlerMethod()) : null); this.executor = executor; this.subscription = this.info.getCoordinates().getTypeName().equalsIgnoreCase("Subscription"); } @@ -524,7 +524,8 @@ public class AnnotatedControllerConfigurer public Object get(DataFetchingEnvironment environment) throws Exception { DataFetcherHandlerMethod handlerMethod = new DataFetcherHandlerMethod( - getHandlerMethod(), this.argumentResolvers, this.validatorHelper, this.executor, this.subscription); + getHandlerMethod(), this.argumentResolvers, this.methodValidationHelper, + this.executor, this.subscription); return handlerMethod.invoke(environment); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/DataFetcherHandlerMethod.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/DataFetcherHandlerMethod.java index e33d9635..6795e842 100644 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/DataFetcherHandlerMethod.java +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/DataFetcherHandlerMethod.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2022 the original author or authors. + * Copyright 2002-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ package org.springframework.graphql.data.method.annotation.support; import java.util.Arrays; import java.util.concurrent.Executor; +import java.util.function.Consumer; import graphql.schema.DataFetchingEnvironment; import org.reactivestreams.Publisher; @@ -49,8 +50,7 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport { private final HandlerMethodArgumentResolverComposite resolvers; - @Nullable - private final HandlerMethodValidationHelper validator; + private final Consumer validationHelper; private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer(); @@ -61,17 +61,17 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport { * Constructor with a parent handler method. * @param handlerMethod the handler method * @param resolvers the argument resolvers - * @param validator the input validator + * @param validationHelper to apply bean validation with * @param subscription whether the field being fetched is of subscription type */ - public DataFetcherHandlerMethod(HandlerMethod handlerMethod, - HandlerMethodArgumentResolverComposite resolvers, @Nullable HandlerMethodValidationHelper validator, - @Nullable Executor executor, boolean subscription) { + public DataFetcherHandlerMethod( + HandlerMethod handlerMethod, HandlerMethodArgumentResolverComposite resolvers, + @Nullable Consumer validationHelper, @Nullable Executor executor, boolean subscription) { super(handlerMethod, executor); Assert.isTrue(!resolvers.getResolvers().isEmpty(), "No argument resolvers"); this.resolvers = resolvers; - this.validator = (validator != null && validator.requiresValidation(handlerMethod) ? validator : null); + this.validationHelper = (validationHelper != null ? validationHelper : args -> {}); this.subscription = subscription; } @@ -83,15 +83,6 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport { return this.resolvers; } - /** - * Return the configured input validator. - * @deprecated as of 1.1 without a replacement - */ - @Deprecated - @Nullable - public HandlerMethodValidationHelper getValidator() { - return this.validator; - } /** @@ -188,9 +179,7 @@ public class DataFetcherHandlerMethod extends InvocableHandlerMethodSupport { @Nullable private Object validateAndInvoke(Object[] args, DataFetchingEnvironment environment) { - if (this.validator != null) { - this.validator.validate(this, args); - } + this.validationHelper.accept(args); return doInvoke(environment.getGraphQlContext(), args); } diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelper.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelper.java deleted file mode 100644 index d34d7a46..00000000 --- a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelper.java +++ /dev/null @@ -1,146 +0,0 @@ -/* - * Copyright 2020-2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.graphql.data.method.annotation.support; - -import java.lang.annotation.Annotation; -import java.util.Set; - -import jakarta.validation.Constraint; -import jakarta.validation.ConstraintViolation; -import jakarta.validation.ConstraintViolationException; -import jakarta.validation.Valid; -import jakarta.validation.Validator; -import jakarta.validation.metadata.BeanDescriptor; - -import org.springframework.context.ApplicationContext; -import org.springframework.core.MethodParameter; -import org.springframework.core.annotation.AnnotationUtils; -import org.springframework.core.annotation.MergedAnnotations; -import org.springframework.graphql.data.method.HandlerMethod; -import org.springframework.lang.Nullable; -import org.springframework.util.Assert; -import org.springframework.validation.annotation.Validated; -import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; -import org.springframework.validation.beanvalidation.SpringValidatorAdapter; - -/** - * Helper class to apply standard bean validation to a {@link HandlerMethod}. - * - * @author Brian Clozel - * @author Rossen Stoyanchev - * @since 1.0 - */ -final class HandlerMethodValidationHelper { - - private final Validator validator; - - - /** - * Constructor with the {@link Validator} instance to use. - */ - public HandlerMethodValidationHelper(Validator validator) { - Assert.notNull(validator, "validator should not be null"); - if (validator instanceof LocalValidatorFactoryBean) { - this.validator = ((LocalValidatorFactoryBean) validator).getValidator(); - } - else if (validator instanceof SpringValidatorAdapter) { - this.validator = validator.unwrap(Validator.class); - } - else { - this.validator = validator; - } - } - - - /** - * Validate the input values to a the {@link HandlerMethod} and throw a - * {@link ConstraintViolationException} in case of violations. - * @param handlerMethod the handler method to validate - * @param arguments the input argument values - */ - public void validate(HandlerMethod handlerMethod, Object[] arguments) { - Set> result = - this.validator.forExecutables().validateParameters( - handlerMethod.getBean(), handlerMethod.getMethod(), arguments, - determineValidationGroups(handlerMethod)); - if (!result.isEmpty()) { - throw new ConstraintViolationException(result); - } - } - - /** - * Determine the validation groups to apply to a handler method, specified - * through the {@link Validated} annotation on the method or on the class. - * @param method the method to check - * @return the applicable validation groups as a Class array - */ - private Class[] determineValidationGroups(HandlerMethod method) { - Validated annotation = findAnnotation(method, Validated.class); - return (annotation != null ? annotation.value() : new Class[0]); - } - - @Nullable - private static A findAnnotation(HandlerMethod method, Class annotationType) { - A annotation = AnnotationUtils.findAnnotation(method.getMethod(), annotationType); - if (annotation == null) { - annotation = AnnotationUtils.findAnnotation(method.getBeanType(), annotationType); - } - return annotation; - } - - /** - * Whether the given method requires standard bean validation. This is the - * case if the method or one of its parameters are annotated with - * {@link Valid} or {@link Validated}, or if any method parameter is declared - * with a {@link Constraint constraint}, or the method parameter type is - * itself {@link BeanDescriptor#isBeanConstrained() constrained}. - * @param method the handler method to check - * @return {@code true} if validation is required, {@code false} otherwise - */ - public boolean requiresValidation(HandlerMethod method) { - if (findAnnotation(method, Validated.class) != null || findAnnotation(method, Valid.class) != null) { - return true; - } - for (MethodParameter parameter : method.getMethodParameters()) { - for (Annotation annotation : parameter.getParameterAnnotations()) { - MergedAnnotations merged = MergedAnnotations.from(annotation); - if (merged.isPresent(Valid.class) || merged.isPresent(Constraint.class) || merged.isPresent(Validated.class)) { - return true; - } - } - Class paramType = parameter.nestedIfOptional().getNestedParameterType(); - if (this.validator.getConstraintsForClass(paramType).isBeanConstrained()) { - return true; - } - } - return false; - } - - - /** - * Factory method for {@link HandlerMethodValidationHelper} if a - * {@link Validator} can be found. - * @param context the context to look up a {@code Validator} bean from - * @return the helper instance, or {@code null - */ - @Nullable - public static HandlerMethodValidationHelper createIfValidatorAvailable(ApplicationContext context) { - Validator validator = context.getBeanProvider(Validator.class).getIfAvailable(); - return (validator != null ? new HandlerMethodValidationHelper(validator) : null); - } - -} diff --git a/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ValidationHelper.java b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ValidationHelper.java new file mode 100644 index 00000000..31cea7e3 --- /dev/null +++ b/spring-graphql/src/main/java/org/springframework/graphql/data/method/annotation/support/ValidationHelper.java @@ -0,0 +1,156 @@ +/* + * Copyright 2002-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.graphql.data.method.annotation.support; + +import java.lang.annotation.Annotation; +import java.util.Set; +import java.util.function.Consumer; + +import jakarta.validation.Constraint; +import jakarta.validation.ConstraintViolation; +import jakarta.validation.ConstraintViolationException; +import jakarta.validation.Valid; +import jakarta.validation.Validator; + +import org.springframework.context.ApplicationContext; +import org.springframework.core.MethodParameter; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.core.annotation.MergedAnnotations; +import org.springframework.graphql.data.method.HandlerMethod; +import org.springframework.lang.Nullable; +import org.springframework.util.Assert; +import org.springframework.validation.annotation.Validated; +import org.springframework.validation.beanvalidation.LocalValidatorFactoryBean; +import org.springframework.validation.beanvalidation.SpringValidatorAdapter; + +/** + * Holds a {@link jakarta.validation.Validator} and helps to create a validation + * callback for a given {@link HandlerMethod} if it is determined that it + * requires bean validation. + * + * @author Rossen Stoyanchev + * @since 1.2 + */ +class ValidationHelper { + + private final Validator validator; + + + private ValidationHelper(Validator validator) { + Assert.notNull(validator, "Validator is required"); + this.validator = validator; + } + + + /** + * Create a validation callback for the given {@link HandlerMethod}, + * possibly {@code null} if the method or the method parameters do not have + * {@link Validated}, {@link Valid}, or {@link Constraint} annotations. + */ + @Nullable + public Consumer getValidationHelperFor(HandlerMethod handlerMethod) { + + boolean required = false; + Class[] groups = null; + + Validated validatedAnnotation = findAnnotation(handlerMethod, Validated.class); + if (validatedAnnotation != null) { + required = true; + groups = validatedAnnotation.value(); + } + else if (findAnnotation(handlerMethod, Valid.class) != null) { + required = true; + } + + for (MethodParameter parameter : handlerMethod.getMethodParameters()) { + if (required) { + break; + } + for (Annotation annot : parameter.getParameterAnnotations()) { + MergedAnnotations merged = MergedAnnotations.from(annot); + if (merged.isPresent(Valid.class) || merged.isPresent(Constraint.class) || merged.isPresent(Validated.class)) { + required = true; + } + } + } + + return (required ? new HandlerMethodValidator(handlerMethod, groups) : null); + } + + @Nullable + private A findAnnotation(HandlerMethod method, Class annotationType) { + A annotation = AnnotationUtils.findAnnotation(method.getMethod(), annotationType); + if (annotation == null) { + annotation = AnnotationUtils.findAnnotation(method.getBeanType(), annotationType); + } + return annotation; + } + + + /** + * Factory method to create a {@link ValidationHelper} if there is a + * {@link Validator} bean declared, or {@code null} otherwise. + */ + @Nullable + public static ValidationHelper createIfValidatorPresent(ApplicationContext context) { + Validator validator = context.getBeanProvider(Validator.class).getIfAvailable(); + if (validator instanceof LocalValidatorFactoryBean) { + validator = ((LocalValidatorFactoryBean) validator).getValidator(); + } + else if (validator instanceof SpringValidatorAdapter) { + validator = validator.unwrap(Validator.class); + } + return (validator != null ? create(validator) : null); + } + + /** + * Factory method with a given {@link Validator} instance. + */ + public static ValidationHelper create(Validator validator) { + return new ValidationHelper(validator); + } + + + /** + * Callback to apply validation to the invocation of a {@link HandlerMethod}. + */ + private class HandlerMethodValidator implements Consumer { + + private final HandlerMethod handlerMethod; + + @Nullable + private final Class[] validationGroups; + + private HandlerMethodValidator(HandlerMethod handlerMethod, @Nullable Class[] validationGroups) { + Assert.notNull(handlerMethod, "HandlerMethod is required"); + this.handlerMethod = handlerMethod; + this.validationGroups = (validationGroups != null ? validationGroups : new Class[] {}); + } + + @Override + public void accept(Object[] arguments) { + + Set> result = + ValidationHelper.this.validator.forExecutables().validateParameters( + this.handlerMethod.getBean(), this.handlerMethod.getMethod(), arguments, this.validationGroups); + + if (!result.isEmpty()) { + throw new ConstraintViolationException(result); + } + } + } + +} diff --git a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelperTests.java b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ValidationHelperTests.java similarity index 58% rename from spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelperTests.java rename to spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ValidationHelperTests.java index 9bec8b52..195b59a5 100644 --- a/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/HandlerMethodValidationHelperTests.java +++ b/spring-graphql/src/test/java/org/springframework/graphql/data/method/annotation/support/ValidationHelperTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the original author or authors. + * Copyright 2020-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.reflect.Method; import java.util.Arrays; -import java.util.Optional; +import java.util.function.Consumer; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; @@ -28,7 +28,6 @@ import jakarta.validation.Valid; import jakarta.validation.Validation; import jakarta.validation.constraints.Max; import jakarta.validation.constraints.NotNull; - import org.assertj.core.api.InstanceOfAssertFactories; import org.assertj.core.api.IterableAssert; import org.assertj.core.api.ThrowableAssert; @@ -39,71 +38,63 @@ import org.springframework.graphql.data.method.HandlerMethod; import org.springframework.validation.annotation.Validated; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatNoException; import static org.assertj.core.api.Assertions.assertThatThrownBy; /** - * Unit tests for {@link HandlerMethodValidationHelper}. + * Unit tests for {@link ValidationHelper}. + * * @author Brian Clozel */ -class HandlerMethodValidationHelperTests { - - private final HandlerMethodValidationHelper validator = - new HandlerMethodValidationHelper(Validation.buildDefaultValidatorFactory().getValidator()); - +class ValidationHelperTests { @Test void shouldFailWithNullValidator() { - assertThatThrownBy(() -> new HandlerMethodValidationHelper(null)).isInstanceOf(IllegalArgumentException.class); + assertThatThrownBy(() -> ValidationHelper.create(null)).isInstanceOf(IllegalArgumentException.class); } @Test void shouldIgnoreMethodsWithoutAnnotations() { - HandlerMethod method = findHandlerMethod(MyBean.class, "notValidatedMethod"); - assertThatNoException().isThrownBy(() -> validator.validate(method, new Object[] {"test", 12})); + Consumer validator = createValidator(MyBean.class, "notValidatedMethod"); + assertThat(validator).isNull(); } @Test void shouldRaiseValidationErrorForAnnotatedParams() { - HandlerMethod method = findHandlerMethod(MyBean.class, "myValidMethod"); - assertViolations(() -> validator.validate(method, new Object[] {null, 2})) + Consumer validator = createValidator(MyBean.class, "myValidMethod"); + assertViolations(() -> validator.accept(new Object[] {null, 2})) .anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethod.arg0")); - assertViolations(() -> validator.validate(method, new Object[] {"test", 12})) + assertViolations(() -> validator.accept(new Object[] {"test", 12})) .anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethod.arg1")); } @Test void shouldRaiseValidationErrorForAnnotatedParamsWithGroups() { - HandlerMethod myValidMethodWithGroup = findHandlerMethod(MyValidationGroupsBean.class, "myValidMethodWithGroup"); - assertViolations(() -> validator.validate(myValidMethodWithGroup, new Object[] {null})) - .anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethodWithGroup.arg0")); + Consumer validator1 = createValidator(MyValidationGroupsBean.class, "myValidMethodWithGroup"); + assertViolation(() -> validator1.accept(new Object[] {null}), "myValidMethodWithGroup.arg0"); - HandlerMethod myValidMethodWithGroupOnType = findHandlerMethod(MyValidationGroupsBean.class, "myValidMethodWithGroupOnType"); - assertViolations(() -> validator.validate(myValidMethodWithGroupOnType, new Object[] {null})) - .anyMatch(violation -> violation.getPropertyPath().toString().equals("myValidMethodWithGroupOnType.arg0")); + Consumer validator2 = createValidator(MyValidationGroupsBean.class, "myValidMethodWithGroupOnType"); + assertViolation(() -> validator2.accept(new Object[] {null}), "myValidMethodWithGroupOnType.arg0"); } @Test void shouldRecognizeMethodsThatRequireValidation() { - HandlerMethod method = findHandlerMethod(RequiresValidationBean.class, "processConstrainedValue"); - assertThat(validator.requiresValidation(method)).isTrue(); + Consumer validator1 = createValidator(RequiresValidationBean.class, "processConstrainedValue"); + assertThat(validator1).isNotNull(); - method = findHandlerMethod(RequiresValidationBean.class, "processValidInput"); - assertThat(validator.requiresValidation(method)).isTrue(); + Consumer validator2 = createValidator(RequiresValidationBean.class, "processValidInput"); + assertThat(validator2).isNotNull(); - method = findHandlerMethod(RequiresValidationBean.class, "processValidatedInput"); - assertThat(validator.requiresValidation(method)).isTrue(); + Consumer validator3 = createValidator(RequiresValidationBean.class, "processValidatedInput"); + assertThat(validator3).isNotNull(); - method = findHandlerMethod(RequiresValidationBean.class, "processInputWithConstrainedValue"); - assertThat(validator.requiresValidation(method)).isTrue(); - - method = findHandlerMethod(RequiresValidationBean.class, "processOptionalInputWithConstrainedValue"); - assertThat(validator.requiresValidation(method)).isTrue(); - - method = findHandlerMethod(RequiresValidationBean.class, "processValue"); - assertThat(validator.requiresValidation(method)).isFalse(); + Consumer validator4 = createValidator(RequiresValidationBean.class, "processValue"); + assertThat(validator4).isNull(); } + private Consumer createValidator(Class handlerType, String methodName) { + return ValidationHelper.create(Validation.buildDefaultValidatorFactory().getValidator()) + .getValidationHelperFor(findHandlerMethod(handlerType, methodName)); + } private HandlerMethod findHandlerMethod(Class handlerType, String methodName) { Object handler = BeanUtils.instantiateClass(handlerType); @@ -121,6 +112,11 @@ class HandlerMethodValidationHelperTests { .asInstanceOf(InstanceOfAssertFactories.iterable(ConstraintViolation.class)); } + private void assertViolation(ThrowableAssert.ThrowingCallable callable, String propertyPath) { + assertViolations(callable).anyMatch(violation -> + violation.getPropertyPath().toString().equals(propertyPath)); + } + @SuppressWarnings("unused") private static class MyBean { @@ -184,34 +180,12 @@ class HandlerMethodValidationHelperTests { public void processValidatedInput(@Validated MyInput input) { } - public void processInputWithConstrainedValue(MyConstrainedInput input) { - } - - public void processOptionalInputWithConstrainedValue(Optional input) { - } - public void processValue(int i) { } - } private static class MyInput { } - private static class MyConstrainedInput { - - @Max(99) - private int i; - - public int getI() { - return this.i; - } - - public void setI(int i) { - this.i = i; - } - - } - }