# HG changeset patch # Parent f3bd519ba87ef6fd695da66b154fb315c355fc7a diff -r f3bd519ba87e java.hints.test/apichanges.xml --- a/java.hints.test/apichanges.xml Mon Mar 11 08:36:39 2013 +0100 +++ b/java.hints.test/apichanges.xml Mon Mar 11 10:38:40 2013 +0100 @@ -107,6 +107,23 @@ + + + Added assertFixes method to HintWarning + + + + + + HintTest.HintWarning.applyFix now enforces constraints for Inspect&Transform. Unless a hint + is marked as Options.QUERY, Options.NO_BULK or Kind.ACTION, + the Fix that is being applied is tested to be JavaFix and repeatable, + which are the requirements of Inspect&Transform. + + + + + Added assertFixes method to HintWarning diff -r f3bd519ba87e java.hints.test/nbproject/project.properties --- a/java.hints.test/nbproject/project.properties Mon Mar 11 08:36:39 2013 +0100 +++ b/java.hints.test/nbproject/project.properties Mon Mar 11 10:38:40 2013 +0100 @@ -1,7 +1,7 @@ is.autoload=true javac.source=1.6 javac.compilerargs=-Xlint -Xlint:-serial -spec.version.base=1.7.0 +spec.version.base=1.8.0 javadoc.arch=${basedir}/arch.xml javadoc.apichanges=${basedir}/apichanges.xml requires.nb.javac=true diff -r f3bd519ba87e java.hints.test/nbproject/project.xml --- a/java.hints.test/nbproject/project.xml Mon Mar 11 08:36:39 2013 +0100 +++ b/java.hints.test/nbproject/project.xml Mon Mar 11 10:38:40 2013 +0100 @@ -129,6 +129,14 @@ + org.netbeans.modules.refactoring.api + + + + 1.34 + + + org.netbeans.spi.editor.hints diff -r f3bd519ba87e java.hints.test/src/org/netbeans/modules/java/hints/test/api/HintTest.java --- a/java.hints.test/src/org/netbeans/modules/java/hints/test/api/HintTest.java Mon Mar 11 08:36:39 2013 +0100 +++ b/java.hints.test/src/org/netbeans/modules/java/hints/test/api/HintTest.java Mon Mar 11 10:38:40 2013 +0100 @@ -93,8 +93,12 @@ import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.JavaSource; import org.netbeans.api.java.source.JavaSource.Phase; +import org.netbeans.api.java.source.ModificationResult; +import org.netbeans.api.java.source.ModificationResult.Difference; import org.netbeans.api.java.source.Task; +import org.netbeans.api.java.source.WorkingCopy; import org.netbeans.api.lexer.Language; +import org.netbeans.api.project.Project; import org.netbeans.core.startup.Main; import org.netbeans.junit.NbTestCase; import org.netbeans.modules.java.JavaDataLoader; @@ -102,20 +106,31 @@ import org.netbeans.modules.java.hints.providers.code.FSWrapper; import org.netbeans.modules.java.hints.providers.code.FSWrapper.ClassWrapper; import org.netbeans.modules.java.hints.providers.spi.HintDescription; +import org.netbeans.modules.java.hints.providers.spi.HintDescription.Worker; +import org.netbeans.modules.java.hints.providers.spi.HintDescriptionFactory; import org.netbeans.modules.java.hints.providers.spi.HintMetadata; +import org.netbeans.modules.java.hints.providers.spi.HintMetadata.Options; +import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl; +import org.netbeans.modules.java.hints.spiimpl.JavaFixImpl.Accessor; import org.netbeans.modules.java.hints.spiimpl.MessageImpl; import org.netbeans.modules.java.hints.spiimpl.SyntheticFix; +import org.netbeans.modules.java.hints.spiimpl.batch.BatchUtilities; import org.netbeans.modules.java.hints.spiimpl.hints.HintsInvoker; import org.netbeans.modules.java.hints.spiimpl.options.HintsSettings; import org.netbeans.modules.java.hints.test.Utilities.TestLookup; +import org.netbeans.modules.java.source.JavaSourceAccessor; import org.netbeans.modules.java.source.TreeLoader; import org.netbeans.modules.parsing.impl.indexing.CacheFolder; import org.netbeans.modules.parsing.impl.indexing.MimeTypes; +import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation; import org.netbeans.spi.editor.hints.ErrorDescription; import org.netbeans.spi.editor.hints.Fix; import org.netbeans.spi.editor.hints.Severity; import org.netbeans.spi.java.classpath.ClassPathProvider; import org.netbeans.spi.java.classpath.support.ClassPathSupport; +import org.netbeans.spi.java.hints.Hint.Kind; +import org.netbeans.spi.java.hints.HintContext; +import org.netbeans.spi.java.hints.JavaFix; import org.netbeans.spi.java.queries.SourceForBinaryQueryImplementation; import org.netbeans.spi.java.queries.SourceLevelQueryImplementation; import org.openide.LifecycleManager; @@ -448,9 +463,36 @@ } List total = new LinkedList(); + final Set requiresJavaFix = Collections.newSetFromMap(new IdentityHashMap()); - for (Collection l : hints.values()) { - total.addAll(l); + for (final Entry> e : hints.entrySet()) { + if ( e.getKey().options.contains(Options.NO_BATCH) + || e.getKey().options.contains(Options.QUERY) + || e.getKey().kind == Kind.ACTION) { + total.addAll(e.getValue()); + continue; + } + for (final HintDescription hd : e.getValue()) { + total.add(HintDescriptionFactory.create() + .setTrigger(hd.getTrigger()) + .setMetadata(e.getKey()) + .setAdditionalConstraints(hd.getAdditionalConstraints()) + .addOptions(hd.getOptions().toArray(new Options[0])) + .setWorker(new Worker() { + @Override public Collection createErrors(HintContext ctx) { + Collection errors = hd.getWorker().createErrors(ctx); + + if (errors != null) { + for (ErrorDescription ed : errors) { + requiresJavaFix.add(ed); + } + } + + return errors; + } + }) + .produce()); + } } CompilationInfo info = parse(testFile); @@ -489,7 +531,7 @@ NbTestCase.assertGC("noone holds javac", cut); DEBUGGING_HELPER.remove(result); - return new HintOutput(result); + return new HintOutput(result, requiresJavaFix); } //must keep the error descriptions (and their Fixes through them) in a field @@ -721,9 +763,11 @@ public final class HintOutput { private final List errors; + private final Set requiresJavaFix; - private HintOutput(List errors) { + private HintOutput(List errors, Set requiresJavaFix) { this.errors = errors; + this.requiresJavaFix = requiresJavaFix; } @@ -809,7 +853,7 @@ assertNotNull("Warning: \"" + warning + "\" not found. All ErrorDescriptions: " + errors.toString(), toFix); - return new HintWarning(toFix); + return new HintWarning(toFix, requiresJavaFix.contains(toFix)); } } @@ -817,8 +861,10 @@ */ public final class HintWarning { private final ErrorDescription warning; - HintWarning(ErrorDescription warning) { + private final boolean requiresJavaFix; + HintWarning(ErrorDescription warning, boolean requiresJavaFix) { this.warning = warning; + this.requiresJavaFix = requiresJavaFix; } /**Applies the only fix of the current warning. Fails if the given warning * does not have exactly one fix. @@ -840,13 +886,7 @@ assertEquals(1, fixes.size()); - Preferences preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class); - preferences.putBoolean("importInnerClasses", true); - try { - fixes.get(0).implement(); - } finally { - preferences.remove("importInnerClasses"); - } + doApplyFix(fixes.get(0)); if (saveAll) LifecycleManager.getDefault().saveAll(); @@ -879,11 +919,59 @@ assertNotNull("Cannot find fix to invoke: " + fixNames.toString(), toApply); - toApply.implement(); + doApplyFix(toApply); + LifecycleManager.getDefault().saveAll(); return new AppliedFix(); } + private void doApplyFix(Fix f) throws Exception { + Preferences preferences = MimeLookup.getLookup(JavaTokenId.language().mimeType()).lookup(Preferences.class); + preferences.putBoolean("importInnerClasses", true); + try { + if (requiresJavaFix) { + assertTrue("The fix must be a JavaFix", f instanceof JavaFixImpl); + + ModificationResult result1 = runJavaFix(((JavaFixImpl) f).jf); + ModificationResult result2 = runJavaFix(((JavaFixImpl) f).jf); + + //ensure the results are the same: + assertEquals("The fix must be repeatable", result1.getModifiedFileObjects(), result2.getModifiedFileObjects()); + + for (FileObject file : result1.getModifiedFileObjects()) { + assertEquals("The fix must be repeatable", result1.getResultingSource(file), result2.getResultingSource(file)); + } + + result1.commit(); + } else { + f.implement(); + } + } finally { + preferences.remove("importInnerClasses"); + } + } + private ModificationResult runJavaFix(final JavaFix jf) throws IOException { + FileObject file = Accessor.INSTANCE.getFile(jf); + JavaSource js = JavaSource.forFileObject(file); + final Map> changes = new HashMap>(); + + ModificationResult mr = js.runModificationTask(new Task() { + public void run(WorkingCopy wc) throws Exception { + if (wc.toPhase(Phase.RESOLVED).compareTo(Phase.RESOLVED) < 0) { + return; + } + + Map resourceContentChanges = new HashMap(); + Accessor.INSTANCE.process(jf, wc, true, resourceContentChanges, /*Ignored for now:*/new ArrayList()); + BatchUtilities.addResourceContentChanges(resourceContentChanges, changes); + + } + }); + + changes.putAll(JavaSourceAccessor.getINSTANCE().getDiffsFromModificationResult(mr)); + + return JavaSourceAccessor.getINSTANCE().createModificationResult(changes, Collections.emptyMap()); + } /**Verifies that the current warning provides the given fixes. * * @param fixes the {@link Fix#getText() } of the expected fixes diff -r f3bd519ba87e java.hints.test/test/unit/src/org/netbeans/modules/java/hints/test/api/HintTestTest.java --- a/java.hints.test/test/unit/src/org/netbeans/modules/java/hints/test/api/HintTestTest.java Mon Mar 11 08:36:39 2013 +0100 +++ b/java.hints.test/test/unit/src/org/netbeans/modules/java/hints/test/api/HintTestTest.java Mon Mar 11 10:38:40 2013 +0100 @@ -55,11 +55,14 @@ import org.netbeans.api.java.source.ClasspathInfo.PathKind; import org.netbeans.api.java.source.CompilationInfo; import org.netbeans.api.java.source.JavaSource; +import org.netbeans.spi.editor.hints.ChangeInfo; import org.netbeans.spi.editor.hints.ErrorDescription; +import org.netbeans.spi.editor.hints.Fix; import org.netbeans.spi.java.hints.ErrorDescriptionFactory; import org.netbeans.spi.java.hints.Hint; import org.netbeans.spi.java.hints.HintContext; import org.netbeans.spi.java.hints.JavaFix; +import org.netbeans.spi.java.hints.JavaFix.TransformationContext; import org.netbeans.spi.java.hints.TriggerTreeKind; import org.openide.cookies.EditorCookie; import org.openide.filesystems.FileObject; @@ -235,4 +238,70 @@ throw new NullPointerException("a"); } } + + @Test + public void testNonJavaFix() throws Exception { + HintTest ht = HintTest.create() + .input("package test;\n" + + "public class Test { }\n"); + try { + ht.run(NonJavaFix.class) + .findWarning("1:0-1:21:verifier:Test") + .applyFix(); + Assert.fail("No exception thrown"); + } catch (AssertionError ae) { + //ok + Assert.assertEquals("The fix must be a JavaFix", ae.getMessage()); + } + } + + @Hint(displayName="nonJavaFix", description="nonJavaFix", category="test") + public static final class NonJavaFix { + @TriggerTreeKind(Kind.CLASS) + public static ErrorDescription hint(HintContext ctx) { + return ErrorDescriptionFactory.forTree(ctx, ctx.getPath(), "Test", new Fix() { + @Override public String getText() { + return "Fix"; + } + @Override public ChangeInfo implement() throws Exception { + return null; + } + }); + } + } + + @Test + public void testNotRepeatableJavaFix() throws Exception { + HintTest ht = HintTest.create() + .input("package test;\n" + + "public class Test { }\n"); + try { + ht.run(NotRepeatableJavaFix.class) + .findWarning("1:0-1:21:verifier:Test") + .applyFix(); + Assert.fail("No exception thrown"); + } catch (AssertionError ae) { + //ok + Assert.assertTrue(ae.getMessage().startsWith("The fix must be repeatable")); + } + } + + @Hint(displayName="notRepeatableJavaFix", description="notRepeatableJavaFix", category="test") + public static final class NotRepeatableJavaFix { + @TriggerTreeKind(Kind.CLASS) + public static ErrorDescription hint(HintContext ctx) { + Fix f = new JavaFix(ctx.getInfo(), ctx.getPath()) { + private boolean wasRun; + @Override protected String getText() { + return "Fix"; + } + @Override protected void performRewrite(TransformationContext ctx) throws Exception { + if (wasRun) return ; + ctx.getWorkingCopy().rewrite(ctx.getPath().getLeaf(), ctx.getWorkingCopy().getTreeMaker().setLabel(ctx.getPath().getLeaf(), "Nue")); + wasRun = true; + } + }.toEditorFix(); + return ErrorDescriptionFactory.forTree(ctx, ctx.getPath(), "Test", f); + } + } }