diff options
14 files changed, 178 insertions, 56 deletions
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java index 035f0d05958..a34185a8d29 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueHandlers.java @@ -73,8 +73,8 @@ public class IssueHandlers implements BatchExtension { } @Override - public boolean isAlive() { - return issue.isAlive(); + public boolean isEndOfLife() { + return issue.isEndOfLife(); } @Override diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java index 007c6589230..8e071fba49e 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/issue/IssueTrackingDecorator.java @@ -26,9 +26,11 @@ import org.sonar.api.batch.*; import org.sonar.api.component.ResourcePerspectives; import org.sonar.api.issue.Issuable; import org.sonar.api.issue.Issue; +import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.api.resources.ResourceUtils; +import org.sonar.api.rules.ActiveRule; import org.sonar.api.rules.Rule; import org.sonar.api.rules.RuleFinder; import org.sonar.api.utils.KeyValueFormat; @@ -55,13 +57,13 @@ public class IssueTrackingDecorator implements Decorator { private final IssueUpdater updater; private final IssueChangeContext changeContext; private final ResourcePerspectives perspectives; - private final RuleFinder ruleFinder; + private final RulesProfile rulesProfile; public IssueTrackingDecorator(IssueCache issueCache, InitialOpenIssuesStack initialOpenIssues, IssueTracking tracking, IssueFilters filters, IssueHandlers handlers, IssueWorkflow workflow, IssueUpdater updater, Project project, ResourcePerspectives perspectives, - RuleFinder ruleFinder) { + RulesProfile rulesProfile) { this.issueCache = issueCache; this.initialOpenIssues = initialOpenIssues; this.tracking = tracking; @@ -71,7 +73,7 @@ public class IssueTrackingDecorator implements Decorator { this.updater = updater; this.changeContext = IssueChangeContext.createScan(project.getAnalysisDate()); this.perspectives = perspectives; - this.ruleFinder = ruleFinder; + this.rulesProfile = rulesProfile; } public boolean shouldExecuteOnProject(Project project) { @@ -131,7 +133,8 @@ public class IssueTrackingDecorator implements Decorator { // non-persisted fields issue.setNew(false); - issue.setAlive(true); + issue.setEndOfLife(false); + issue.setOnDisabledRule(false); // fields to update with old values issue.setActionPlanKey(ref.getActionPlanKey()); @@ -162,13 +165,14 @@ public class IssueTrackingDecorator implements Decorator { private void addUnmatched(Collection<IssueDto> unmatchedIssues, Collection<DefaultIssue> issues) { for (IssueDto unmatchedDto : unmatchedIssues) { DefaultIssue unmatched = unmatchedDto.toDefaultIssue(); - unmatched.setNew(false); - Rule rule = ruleFinder.findByKey(unmatched.ruleKey()); + ActiveRule activeRule = rulesProfile.getActiveRule(unmatchedDto.getRuleRepo(), unmatchedDto.getRule()); boolean manualIssue = !Strings.isNullOrEmpty(unmatched.reporter()); - boolean onExistingRule = (rule != null && !Rule.STATUS_REMOVED.equals(rule.getStatus())); - unmatched.setAlive(manualIssue && onExistingRule); + boolean onDisabledRule = (activeRule==null); + unmatched.setNew(false); + unmatched.setEndOfLife(!manualIssue); + unmatched.setOnDisabledRule(onDisabledRule); issues.add(unmatched); } } @@ -176,8 +180,10 @@ public class IssueTrackingDecorator implements Decorator { private void addDead(Collection<DefaultIssue> issues) { for (IssueDto deadDto : initialOpenIssues.getAllIssues()) { DefaultIssue dead = deadDto.toDefaultIssue(); - dead.setAlive(false); + ActiveRule activeRule = rulesProfile.getActiveRule(deadDto.getRuleRepo(), deadDto.getRule()); dead.setNew(false); + dead.setEndOfLife(true); + dead.setOnDisabledRule(activeRule==null); issues.add(dead); } } diff --git a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties index 82a60c4ce95..5f443c280ef 100644 --- a/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties +++ b/plugins/sonar-core-plugin/src/main/resources/org/sonar/l10n/core.properties @@ -553,9 +553,9 @@ issue.status.RESOLVED=Resolved issue.status.OPEN=Open issue.status.CONFIRMED=Confirmed issue.status.CLOSED=Closed -issue.resolution.OPEN=Open issue.resolution.FALSE-POSITIVE=False-positive issue.resolution.FIXED=Fixed +issue.resolution.REMOVED=Removed issue.planned_for_x=Planned for {0} issue.planned_for=Planned for issue.manual.missing_rule=Missing rule diff --git a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java index 8df79fd5c3a..bc062f6a0ce 100644 --- a/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java +++ b/plugins/sonar-core-plugin/src/test/java/org/sonar/plugins/core/issue/IssueTrackingDecoratorTest.java @@ -24,6 +24,7 @@ import org.junit.Test; import org.mockito.ArgumentMatcher; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.component.ResourcePerspectives; +import org.sonar.api.profiles.RulesProfile; import org.sonar.api.resources.File; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; @@ -54,7 +55,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { IssueUpdater updater = mock(IssueUpdater.class); ResourcePerspectives perspectives = mock(ResourcePerspectives.class); Date loadedDate = new Date(); - RuleFinder ruleFinder = mock(RuleFinder.class); + RulesProfile profile = mock(RulesProfile.class); @Before public void init() { @@ -68,7 +69,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { updater, mock(Project.class), perspectives, - ruleFinder); + profile); } @Test @@ -170,7 +171,7 @@ public class IssueTrackingDecoratorTest extends AbstractDaoTestCase { @Override public boolean matches(Object o) { DefaultIssue dead = (DefaultIssue) o; - return "ABCDE".equals(dead.key()) && !dead.isNew() && !dead.isAlive(); + return "ABCDE".equals(dead.key()) && !dead.isNew() && dead.isEndOfLife(); } })); } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java index a020f13445b..56fb83bebe4 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssue.java @@ -74,9 +74,11 @@ public class DefaultIssue implements Issue { // true if the the issue did not exist in the previous scan. private boolean isNew = true; - // true if the the issue did exist in the previous scan but not in the current one. That means + // True if the the issue did exist in the previous scan but not in the current one. That means // that this issue should be closed. - private boolean isAlive = true; + private boolean endOfLife = false; + + private boolean onDisabledRule = false; // true if some fields have been changed since the previous scan private boolean isChanged = false; @@ -257,12 +259,21 @@ public class DefaultIssue implements Issue { return this; } - public boolean isAlive() { - return isAlive; + public boolean isEndOfLife() { + return endOfLife; + } + + public DefaultIssue setEndOfLife(boolean b) { + endOfLife = b; + return this; + } + + public boolean isOnDisabledRule() { + return onDisabledRule; } - public DefaultIssue setAlive(boolean b) { - isAlive = b; + public DefaultIssue setOnDisabledRule(boolean b) { + onDisabledRule = b; return this; } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java index 0546585f7aa..e3ec577a3ca 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/DefaultIssueBuilder.java @@ -139,6 +139,9 @@ public class DefaultIssueBuilder implements Issuable.IssueBuilder { issue.setAttributes(attributes); issue.setResolution(null); issue.setStatus(Issue.STATUS_OPEN); + issue.setNew(true); + issue.setEndOfLife(false); + issue.setOnDisabledRule(false); return issue; } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsAlive.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsEndOfLife.java index 7a3179c5cd0..741f7dbbd40 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsAlive.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IsEndOfLife.java @@ -22,16 +22,16 @@ package org.sonar.core.issue.workflow; import org.sonar.api.issue.Issue; import org.sonar.core.issue.DefaultIssue; -class IsAlive implements Condition { +class IsEndOfLife implements Condition { - private final boolean alive; + private final boolean endOfLife; - IsAlive(boolean alive) { - this.alive = alive; + IsEndOfLife(boolean endOfLife) { + this.endOfLife = endOfLife; } @Override public boolean matches(Issue issue) { - return ((DefaultIssue) issue).isAlive() == alive; + return ((DefaultIssue) issue).isEndOfLife() == endOfLife; } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java index 4b4780f4122..f6b8dc2511d 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/IssueWorkflow.java @@ -81,7 +81,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable .functions(new SetResolution(null), new SetCloseDate(false)) .build()) - // resolve as false-positive + // resolve as false-positive .transition(Transition.builder(DefaultTransitions.FALSE_POSITIVE) .from(Issue.STATUS_OPEN).to(Issue.STATUS_RESOLVED) .conditions(new IsManual(false)) @@ -100,37 +100,37 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable // automatic transitions - // Close the issues that do not exist anymore. Note that isAlive() is true on manual issues + // Close the issues that do not exist anymore. Note that isEndOfLife() is false on manual issues .transition(Transition.builder("automaticclose") .from(Issue.STATUS_OPEN).to(Issue.STATUS_CLOSED) - .conditions(new IsAlive(false)) - .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetCloseDate(true)) + .conditions(new IsEndOfLife(true)) + .functions(new SetEndOfLifeResolution(), new SetCloseDate(true)) .automatic() .build()) .transition(Transition.builder("automaticclose") .from(Issue.STATUS_REOPENED).to(Issue.STATUS_CLOSED) - .conditions(new IsAlive(false)) - .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetCloseDate(true)) + .conditions(new IsEndOfLife(true)) + .functions(new SetEndOfLifeResolution(), new SetCloseDate(true)) .automatic() .build()) .transition(Transition.builder("automaticclose") .from(Issue.STATUS_CONFIRMED).to(Issue.STATUS_CLOSED) - .conditions(new IsAlive(false)) - .functions(new SetResolution(Issue.RESOLUTION_FIXED), new SetCloseDate(true)) + .conditions(new IsEndOfLife(true)) + .functions(new SetEndOfLifeResolution(), new SetCloseDate(true)) .automatic() .build()) // Close the issues marked as resolved and that do not exist anymore. // Note that false-positives are kept resolved and are not closed. .transition(Transition.builder("automaticclose") .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_CLOSED) - .conditions(new IsAlive(false)) - .functions(new SetCloseDate(true)) + .conditions(new IsEndOfLife(true)) + .functions(new SetEndOfLifeResolution(), new SetCloseDate(true)) .automatic() .build()) .transition(Transition.builder("automaticreopen") .from(Issue.STATUS_RESOLVED).to(Issue.STATUS_REOPENED) - .conditions(new IsAlive(true), new HasResolution(Issue.RESOLUTION_FIXED)) - .functions(new SetResolution(null)) + .conditions(new IsEndOfLife(false), new HasResolution(Issue.RESOLUTION_FIXED)) + .functions(new SetResolution(null), new SetCloseDate(false)) .automatic() .build()) .build(); @@ -168,7 +168,7 @@ public class IssueWorkflow implements BatchComponent, ServerComponent, Startable private State stateOf(DefaultIssue issue) { State state = machine.state(issue.status()); - if (state==null) { + if (state == null) { throw new IllegalStateException("Unknown status: " + issue.status() + " [issue=" + issue.key() + "]"); } return state; diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetEndOfLifeResolution.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetEndOfLifeResolution.java new file mode 100644 index 00000000000..387e69f6aea --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/SetEndOfLifeResolution.java @@ -0,0 +1,38 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.issue.workflow; + +import org.sonar.api.issue.Issue; +import org.sonar.core.issue.DefaultIssue; + +public class SetEndOfLifeResolution implements Function { + @Override + public void execute(Context context) { + DefaultIssue issue = (DefaultIssue) context.issue(); + if (!issue.isEndOfLife()) { + throw new IllegalStateException("Issue is still alive: " + issue); + } + if (issue.isOnDisabledRule()) { + context.setResolution(Issue.RESOLUTION_REMOVED); + } else { + context.setResolution(Issue.RESOLUTION_FIXED); + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsAliveTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsEndOfLifeTest.java index 6bc1b4197e8..bcddc94c565 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsAliveTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IsEndOfLifeTest.java @@ -24,20 +24,20 @@ import org.sonar.core.issue.DefaultIssue; import static org.fest.assertions.Assertions.assertThat; -public class IsAliveTest { +public class IsEndOfLifeTest { DefaultIssue issue = new DefaultIssue(); @Test - public void should_match_alive() throws Exception { - IsAlive condition = new IsAlive(true); - assertThat(condition.matches(issue.setAlive(true))).isTrue(); - assertThat(condition.matches(issue.setAlive(false))).isFalse(); + public void should_be_end_of_life() throws Exception { + IsEndOfLife condition = new IsEndOfLife(true); + assertThat(condition.matches(issue.setEndOfLife(true))).isTrue(); + assertThat(condition.matches(issue.setEndOfLife(false))).isFalse(); } @Test - public void should_match_dead() throws Exception { - IsAlive condition = new IsAlive(false); - assertThat(condition.matches(issue.setAlive(true))).isFalse(); - assertThat(condition.matches(issue.setAlive(false))).isTrue(); + public void should_not_be_end_of_life() throws Exception { + IsEndOfLife condition = new IsEndOfLife(false); + assertThat(condition.matches(issue.setEndOfLife(true))).isFalse(); + assertThat(condition.matches(issue.setEndOfLife(false))).isTrue(); } } diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java index ae976307c3c..ec9375826b1 100644 --- a/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java +++ b/sonar-core/src/test/java/org/sonar/core/issue/workflow/IssueWorkflowTest.java @@ -114,7 +114,7 @@ public class IssueWorkflowTest { .setResolution(Issue.RESOLUTION_FIXED) .setStatus(Issue.STATUS_RESOLVED) .setNew(false) - .setAlive(false); + .setEndOfLife(true); Date now = new Date(); workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED); @@ -132,7 +132,7 @@ public class IssueWorkflowTest { .setResolution(null) .setStatus(Issue.STATUS_OPEN) .setNew(false) - .setAlive(false); + .setEndOfLife(true); Date now = new Date(); workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED); @@ -150,7 +150,7 @@ public class IssueWorkflowTest { .setResolution(null) .setStatus(Issue.STATUS_REOPENED) .setNew(false) - .setAlive(false); + .setEndOfLife(true); Date now = new Date(); workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED); @@ -168,7 +168,7 @@ public class IssueWorkflowTest { .setResolution(null) .setStatus(Issue.STATUS_CONFIRMED) .setNew(false) - .setAlive(false); + .setEndOfLife(true); Date now = new Date(); workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(now)); assertThat(issue.resolution()).isEqualTo(Issue.RESOLUTION_FIXED); @@ -187,7 +187,7 @@ public class IssueWorkflowTest { .setResolution(Issue.RESOLUTION_FIXED) .setStatus("xxx") .setNew(false) - .setAlive(false); + .setEndOfLife(true); try { workflow.doAutomaticTransition(issue, IssueChangeContext.createScan(new Date())); fail(); diff --git a/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetEndOfLifeResolutionTest.java b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetEndOfLifeResolutionTest.java new file mode 100644 index 00000000000..85268ab285d --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/issue/workflow/SetEndOfLifeResolutionTest.java @@ -0,0 +1,63 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2013 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.issue.workflow; + +import org.junit.Test; +import org.sonar.api.issue.Issue; +import org.sonar.core.issue.DefaultIssue; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.*; + +public class SetEndOfLifeResolutionTest { + + Function.Context context = mock(Function.Context.class); + SetEndOfLifeResolution function = new SetEndOfLifeResolution(); + + @Test + public void should_resolve_as_fixed() throws Exception { + Issue issue = new DefaultIssue().setEndOfLife(true).setOnDisabledRule(false); + when(context.issue()).thenReturn(issue); + function.execute(context); + verify(context, times(1)).setResolution(Issue.RESOLUTION_FIXED); + } + + @Test + public void should_resolve_as_removed_when_rule_is_disabled() throws Exception { + Issue issue = new DefaultIssue().setEndOfLife(true).setOnDisabledRule(true); + when(context.issue()).thenReturn(issue); + function.execute(context); + verify(context, times(1)).setResolution(Issue.RESOLUTION_REMOVED); + } + + @Test + public void should_fail_if_issue_is_not_resolved() throws Exception { + Issue issue = new DefaultIssue().setEndOfLife(false); + when(context.issue()).thenReturn(issue); + try { + function.execute(context); + fail(); + } catch (IllegalStateException e) { + assertThat(e.getMessage()).contains("Issue is still alive"); + verify(context, never()).setResolution(anyString()); + } + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java index a517eaa7111..7f0fab7d2d2 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/Issue.java @@ -44,8 +44,9 @@ public interface Issue extends Serializable { String RESOLUTION_FIXED = "FIXED"; String RESOLUTION_FALSE_POSITIVE = "FALSE-POSITIVE"; + String RESOLUTION_REMOVED = "REMOVED"; - List<String> RESOLUTIONS = ImmutableList.of(RESOLUTION_FALSE_POSITIVE, RESOLUTION_FIXED); + List<String> RESOLUTIONS = ImmutableList.of(RESOLUTION_FALSE_POSITIVE, RESOLUTION_FIXED, RESOLUTION_REMOVED); /** * Unique generated key diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java index 8835ddfba8f..12f4a5c3fdd 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/issue/IssueHandler.java @@ -33,8 +33,7 @@ public interface IssueHandler extends BatchExtension { boolean isNew(); - // TODO rename isClosed() - boolean isAlive(); + boolean isEndOfLife(); Context setLine(@Nullable Integer i); |