diff --git a/java/java-tests/testData/codeEditor/smartPsiElementPointers/AClass.java b/java/java-tests/testData/codeEditor/smartPsiElementPointers/AClass.java new file mode 100644 index 000000000000..777991de5f24 --- /dev/null +++ b/java/java-tests/testData/codeEditor/smartPsiElementPointers/AClass.java @@ -0,0 +1,2 @@ +class AClass{ +} diff --git a/java/java-tests/testData/codeEditor/smartPsiElementPointers/Test.java b/java/java-tests/testData/codeEditor/smartPsiElementPointers/Test.java new file mode 100644 index 000000000000..2645b39f216f --- /dev/null +++ b/java/java-tests/testData/codeEditor/smartPsiElementPointers/Test.java @@ -0,0 +1,3 @@ +class Test { + AClass field; +} \ No newline at end of file diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test.ipr b/java/java-tests/testData/codeInsight/navigation/alexProject/test.ipr new file mode 100644 index 000000000000..dded6f40d0bb --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test.ipr @@ -0,0 +1,264 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestI.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestI.java new file mode 100644 index 000000000000..11b68571b9b3 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestI.java @@ -0,0 +1,12 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:35:18 PM + * To change this template use File | Settings | File Templates. + */ +public interface TestI {//jjjkjh kljh lk + void test(/* */); +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl1.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl1.java new file mode 100644 index 000000000000..d06b163e5806 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl1.java @@ -0,0 +1,14 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:35:52 PM + * To change this template use File | Settings | File Templates. + */ +public class TestIImpl1 implements TestI { + public void test() { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl2.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl2.java new file mode 100644 index 000000000000..fd221c978f26 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/src/com/test/TestIImpl2.java @@ -0,0 +1,14 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:36:03 PM + * To change this template use File | Settings | File Templates. + */ +public class TestIImpl2 implements TestI { + public void test() { + //To change body of implemented methods use File | Settings | File Templates. + } +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test1/test1.iml b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/test1.iml new file mode 100644 index 000000000000..9c3a0a64d620 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test1/test1.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestI.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestI.java new file mode 100644 index 000000000000..7b22b82f085e --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestI.java @@ -0,0 +1,11 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:35:11 PM + * To change this template use File | Settings | File Templates. + */ +public interface TestI { +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl1.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl1.java new file mode 100644 index 000000000000..68617fc3a4e9 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl1.java @@ -0,0 +1,11 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:36:22 PM + * To change this template use File | Settings | File Templates. + */ +public class TestIImpl1 implements TestI { +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl3.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl3.java new file mode 100644 index 000000000000..7ae76c86ae6e --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/src/com/test/TestIImpl3.java @@ -0,0 +1,11 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:36:28 PM + * To change this template use File | Settings | File Templates. + */ +public class TestIImpl3 implements TestI { +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test2/test2.iml b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/test2.iml new file mode 100644 index 000000000000..9c3a0a64d620 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test2/test2.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestI.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestI.java new file mode 100644 index 000000000000..f3d43e0be395 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestI.java @@ -0,0 +1,11 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:35:03 PM + * To change this template use File | Settings | File Templates. + */ +public interface TestI { +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestIImpl1.java b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestIImpl1.java new file mode 100644 index 000000000000..56e5d849b9cc --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/src/com/test/TestIImpl1.java @@ -0,0 +1,11 @@ +package com.test; + +/** + * Created by IntelliJ IDEA. + * User: cdr + * Date: Aug 14, 2006 + * Time: 3:36:36 PM + * To change this template use File | Settings | File Templates. + */ +public class TestIImpl1 implements TestI { +} diff --git a/java/java-tests/testData/codeInsight/navigation/alexProject/test3/test3.iml b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/test3.iml new file mode 100644 index 000000000000..9c3a0a64d620 --- /dev/null +++ b/java/java-tests/testData/codeInsight/navigation/alexProject/test3/test3.iml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/java/java-tests/testData/psi/libraryOrder/lib1/classes/test/A.class b/java/java-tests/testData/psi/libraryOrder/lib1/classes/test/A.class new file mode 100644 index 000000000000..27ab0077801c Binary files /dev/null and b/java/java-tests/testData/psi/libraryOrder/lib1/classes/test/A.class differ diff --git a/java/java-tests/testData/psi/libraryOrder/lib1/src/test/A.java b/java/java-tests/testData/psi/libraryOrder/lib1/src/test/A.java new file mode 100644 index 000000000000..691fe65b3481 --- /dev/null +++ b/java/java-tests/testData/psi/libraryOrder/lib1/src/test/A.java @@ -0,0 +1,5 @@ +package test; +class A { + void methodOfClassFromLib1() { + } +} \ No newline at end of file diff --git a/java/java-tests/testData/psi/libraryOrder/lib2/classes/test/A.class b/java/java-tests/testData/psi/libraryOrder/lib2/classes/test/A.class new file mode 100644 index 000000000000..e1a46402d172 Binary files /dev/null and b/java/java-tests/testData/psi/libraryOrder/lib2/classes/test/A.class differ diff --git a/java/java-tests/testData/psi/libraryOrder/lib2/src/test/A.java b/java/java-tests/testData/psi/libraryOrder/lib2/src/test/A.java new file mode 100644 index 000000000000..9f54891a0f04 --- /dev/null +++ b/java/java-tests/testData/psi/libraryOrder/lib2/src/test/A.java @@ -0,0 +1,5 @@ +package test; +class A { + void methodOfClassFromLib2() { + } +} \ No newline at end of file diff --git a/java/java-tests/testSrc/com/intellij/navigation/GotoImplementationTest.java b/java/java-tests/testSrc/com/intellij/navigation/GotoImplementationTest.java new file mode 100644 index 000000000000..b63f3672a9aa --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/navigation/GotoImplementationTest.java @@ -0,0 +1,92 @@ +package com.intellij.navigation; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.CodeInsightTestCase; +import com.intellij.codeInsight.navigation.ClassImplementationsSearch; +import com.intellij.codeInsight.navigation.MethodImplementationsSearch; +import com.intellij.openapi.module.Module; +import com.intellij.openapi.module.ModuleManager; +import com.intellij.openapi.module.impl.ModuleManagerImpl; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.util.io.FileUtil; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.PsiClass; +import com.intellij.psi.PsiMethod; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.util.CommonProcessors; + +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * @author cdr + */ +public class GotoImplementationTest extends CodeInsightTestCase { + + private static Collection getClassImplementations(final PsiClass psiClass) { + CommonProcessors.CollectProcessor processor = new CommonProcessors.CollectProcessor(); + ClassImplementationsSearch.processImplementations(psiClass, processor); + + return processor.getResults(); + } + + @Override + protected void setUpProject() throws Exception { + final String root = JavaTestUtil.getJavaTestDataPath() + "/codeInsight/navigation/alexProject"; + VirtualFile vfsRoot = LocalFileSystem.getInstance().findFileByPath(FileUtil.toSystemIndependentName(root)); + + VirtualFile projectFile = vfsRoot.findChild("test.ipr"); + myProject = ProjectManagerEx.getInstanceEx().loadProject(projectFile.getPath()); + + simulateProjectOpen(); + ProjectManagerEx.getInstanceEx().setCurrentTestProject(myProject); + } + + @Override + protected void tearDown() throws Exception { + ((ModuleManagerImpl)ModuleManager.getInstance(myProject)).projectClosed(); + super.tearDown(); + } + + public void test() throws Exception { + + ModuleManager moduleManager = ModuleManager.getInstance(getProject()); + Module[] modules = moduleManager.getModules(); + assertEquals(3, modules.length); + + Module module1 = moduleManager.findModuleByName("test1"); + Module module2 = moduleManager.findModuleByName("test2"); + Module module3 = moduleManager.findModuleByName("test3"); + PsiClass test1 = myJavaFacade.findClass("com.test.TestI", GlobalSearchScope.moduleScope(module1)); + PsiClass test2 = myJavaFacade.findClass("com.test.TestI", GlobalSearchScope.moduleScope(module2)); + PsiClass test3 = myJavaFacade.findClass("com.test.TestI", GlobalSearchScope.moduleScope(module3)); + HashSet expectedImpls1 = new HashSet(Arrays.asList( + myJavaFacade.findClass("com.test.TestIImpl1", GlobalSearchScope.moduleScope(module1)), + myJavaFacade.findClass("com.test.TestIImpl2", GlobalSearchScope.moduleScope(module1)) + )); + assertEquals(expectedImpls1, new HashSet(getClassImplementations(test1))); + + PsiMethod psiMethod = test1.findMethodsByName("test", false)[0]; + Set expectedMethodImpl1 = new HashSet(Arrays.asList( + myJavaFacade.findClass("com.test.TestIImpl1", GlobalSearchScope.moduleScope(module1)).findMethodsByName("test",false)[0], + myJavaFacade.findClass("com.test.TestIImpl2", GlobalSearchScope.moduleScope(module1)).findMethodsByName("test",false)[0] + )); + assertEquals(expectedMethodImpl1, new HashSet(Arrays.asList(MethodImplementationsSearch.getMethodImplementations(psiMethod)))); + + HashSet expectedImpls2 = new HashSet(Arrays.asList( + myJavaFacade.findClass("com.test.TestIImpl1", GlobalSearchScope.moduleScope(module2)), + myJavaFacade.findClass("com.test.TestIImpl3", GlobalSearchScope.moduleScope(module2)) + )); + assertEquals(expectedImpls2, new HashSet(getClassImplementations(test2))); + + HashSet expectedImpls3 = new HashSet(Arrays.asList( + myJavaFacade.findClass("com.test.TestIImpl1", GlobalSearchScope.moduleScope(module3)) + )); + assertEquals(expectedImpls3, new HashSet(getClassImplementations(test3))); + + } + +} diff --git a/java/java-tests/testSrc/com/intellij/psi/LibraryOrderTest.java b/java/java-tests/testSrc/com/intellij/psi/LibraryOrderTest.java new file mode 100644 index 000000000000..1e288c2acbc4 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/LibraryOrderTest.java @@ -0,0 +1,110 @@ +package com.intellij.psi; + +import com.intellij.JavaTestUtil; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.roots.*; +import com.intellij.openapi.roots.libraries.Library; +import com.intellij.openapi.roots.libraries.LibraryTable; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.testFramework.PsiTestCase; + +import java.io.File; +import java.util.Arrays; +import java.util.List; + +/** + * @author dsl + */ +public class LibraryOrderTest extends PsiTestCase { + + public void test1() { + setupPaths(); + checkClassFromLib("test.A", "1"); + + final ModifiableRootModel rootModel = ModuleRootManager.getInstance(myModule).getModifiableModel(); + final OrderEntry[] order = rootModel.getOrderEntries(); + final int length = order.length; + OrderEntry lib2 = order[length - 1]; + OrderEntry lib1 = order[length - 2]; + assertTrue(lib1 instanceof LibraryOrderEntry); + assertEquals("lib1", ((LibraryOrderEntry) lib1).getLibraryName()); + assertTrue(lib2 instanceof LibraryOrderEntry); + assertEquals("lib2", ((LibraryOrderEntry) lib2).getLibraryName()); + + order[length - 1] = lib1; + order[length - 2] = lib2; + rootModel.rearrangeOrderEntries(order); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + rootModel.commit(); + } + } + ); + + checkClassFromLib("test.A", "2"); + } + + public void testNavigation() throws Exception { + setupPaths(); + final JavaPsiFacade psiManager = getJavaFacade(); + final PsiClass classA = psiManager.findClass("test.A"); + final PsiElement navigationElement = classA.getNavigationElement(); + assertNotNull(navigationElement); + assertTrue(navigationElement != classA); + assertEquals("A.java", navigationElement.getContainingFile().getVirtualFile().getName()); + } + + private void checkClassFromLib(String qualifiedName, String index) { + final PsiClass classA = (PsiClass)JavaPsiFacade.getInstance(myProject).findClass(qualifiedName).getNavigationElement(); + assertNotNull(classA); + final PsiMethod[] methodsA = classA.getMethods(); + assertEquals(1, methodsA.length); + assertEquals("methodOfClassFromLib" + index, methodsA[0].getName()); + } + + public void setupPaths() { + final String basePath = JavaTestUtil.getJavaTestDataPath() + "/psi/libraryOrder/"; + + final VirtualFile lib1SrcFile = refreshAndFindFile(basePath + "lib1/src"); + final VirtualFile lib1classes = refreshAndFindFile(basePath + "lib1/classes"); + final VirtualFile lib2SrcFile = refreshAndFindFile(basePath + "lib2/src"); + final VirtualFile lib2classes = refreshAndFindFile(basePath + "lib2/classes"); + + assertTrue(lib1SrcFile != null); + assertTrue(lib2SrcFile != null); + + final ModifiableRootModel rootModel = ModuleRootManager.getInstance(myModule).getModifiableModel(); + final LibraryTable libraryTable = rootModel.getModuleLibraryTable(); + + addLibraryWithSourcePath("lib1", libraryTable, lib1SrcFile, lib1classes); + addLibraryWithSourcePath("lib2", libraryTable, lib2SrcFile, lib2classes); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + @Override + public void run() { + rootModel.commit(); + } + }); + final List list = Arrays.asList(OrderEnumerator.orderEntries(myModule).getClassesRoots()); + assertTrue(list.contains(lib1classes)); + assertTrue(list.contains(lib2classes)); + } + + private VirtualFile refreshAndFindFile(String path) { + final File ioLib1Src = new File(path); + final VirtualFile lib1SrcFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(ioLib1Src); + return lib1SrcFile; + } + + private void addLibraryWithSourcePath(String name, final LibraryTable libraryTable, final VirtualFile libSource, + VirtualFile libClasses) { + final Library lib = libraryTable.createLibrary(name); + final Library.ModifiableModel libModel = lib.getModifiableModel(); + libModel.addRoot(libClasses, OrderRootType.CLASSES); + libModel.addRoot(libSource, OrderRootType.SOURCES); + libModel.commit(); + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/PsiDocumentManagerImplTest.java b/java/java-tests/testSrc/com/intellij/psi/PsiDocumentManagerImplTest.java new file mode 100644 index 000000000000..b5ff32ecc0ab --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/PsiDocumentManagerImplTest.java @@ -0,0 +1,298 @@ +package com.intellij.psi; + +import com.intellij.ide.impl.ProjectUtil; +import com.intellij.mock.MockDocument; +import com.intellij.mock.MockPsiFile; +import com.intellij.openapi.application.ApplicationManager; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.impl.DocumentImpl; +import com.intellij.openapi.editor.impl.event.DocumentEventImpl; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileEditor.impl.FileDocumentManagerImpl; +import com.intellij.openapi.project.Project; +import com.intellij.openapi.project.ex.ProjectManagerEx; +import com.intellij.openapi.vfs.LocalFileSystem; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.psi.impl.PsiDocumentManagerImpl; +import com.intellij.psi.impl.TextBlock; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.testFramework.LeakHunter; +import com.intellij.testFramework.LightVirtualFile; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.util.Processor; +import com.intellij.util.concurrency.Semaphore; +import com.intellij.util.ui.UIUtil; + +import java.io.File; +import java.lang.ref.Reference; +import java.util.concurrent.atomic.AtomicInteger; + +public class PsiDocumentManagerImplTest extends PlatformTestCase { + private PsiDocumentManagerImpl getPsiDocumentManager() { + return (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(getProject()); + } + + public void testGetCachedPsiFile_NoFile() throws Exception { + final PsiFile file = getPsiDocumentManager().getCachedPsiFile(new MockDocument()); + assertNull(file); + } + + public void testGetPsiFile_NotRegisteredDocument() throws Exception { + final PsiFile file = getPsiDocumentManager().getPsiFile(new MockDocument()); + assertNull(file); + } + + public void testGetDocument_FirstGet() throws Exception { + VirtualFile vFile = createFile(); + final PsiFile file = new MockPsiFile(vFile, getPsiManager()); + + final Document document = getPsiDocumentManager().getDocument(file); + assertNotNull(document); + assertSame(document, FileDocumentManager.getInstance().getDocument(vFile)); + } + + private static LightVirtualFile createFile() { + return new LightVirtualFile("foo.java"); + } + + public void testDocumentGced() throws Exception { + VirtualFile vFile = createFile(); + PsiDocumentManagerImpl documentManager = getPsiDocumentManager(); + long id = System.identityHashCode(documentManager.getDocument(getPsiManager().findFile(vFile))); + + documentManager.commitAllDocuments(); + UIUtil.dispatchAllInvocationEvents(); + UIUtil.dispatchAllInvocationEvents(); + assertEmpty(documentManager.getUncommittedDocuments()); + + LeakHunter.checkLeak(documentManager, DocumentImpl.class); + LeakHunter.checkLeak(documentManager, PsiFileImpl.class, new Processor() { + @Override + public boolean process(PsiFileImpl psiFile) { + return psiFile.getViewProvider().getVirtualFile().getFileSystem() instanceof LocalFileSystem; + } + }); + //Class.forName("com.intellij.util.ProfilingUtil").getDeclaredMethod("forceCaptureMemorySnapshot").invoke(null); + + Reference reference = vFile.getUserData(FileDocumentManagerImpl.DOCUMENT_KEY); + assertNotNull(reference); + for (int i=0;i<1000;i++) { + UIUtil.dispatchAllInvocationEvents(); + if (reference.get() == null) break; + System.gc(); + } + assertNull(documentManager.getCachedDocument(getPsiManager().findFile(vFile))); + + Document newDoc = documentManager.getDocument(getPsiManager().findFile(vFile)); + assertTrue(id != System.identityHashCode(newDoc)); + } + + public void testGetUncommittedDocuments_noDocuments() throws Exception { + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + } + + public void testGetUncommittedDocuments_documentChanged_DontProcessEvents() throws Exception { + PsiFile file = getPsiManager().findFile(createFile()); + + final Document document = getPsiDocumentManager().getDocument(file); + + final TextBlock block = TextBlock.get(file); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + block.performAtomically(new Runnable() { + @Override + public void run() { + getPsiDocumentManager().documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + } + }); + } + }); + + + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + } + + public void testGetUncommittedDocuments_documentNotRegistered() throws Exception { + final Document document = new MockDocument(); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + getPsiDocumentManager().documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + } + }); + + + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + } + + public void testCommitDocument_RemovesFromUncommittedList() throws Exception { + PsiFile file = getPsiManager().findFile(createFile()); + + final Document document = getPsiDocumentManager().getDocument(file); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + getPsiDocumentManager().documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + } + }); + + + getPsiDocumentManager().commitDocument(document); + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + } + + public void testCommitAllDocument_RemovesFromUncommittedList() throws Exception { + PsiFile file = getPsiManager().findFile(createFile()); + + final Document document = getPsiDocumentManager().getDocument(file); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + getPsiDocumentManager().documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + } + }); + + + getPsiDocumentManager().commitAllDocuments(); + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + } + + public void testDocumentFromAlienProjectDoesNotEndsUpInMyUncommittedList() throws Exception { + PsiFile file = getPsiManager().findFile(createFile()); + + final Document document = getPsiDocumentManager().getDocument(file); + + File temp = createTempDirectory(); + final Project alienProject = createProject(new File(temp, "alien.ipr"), DebugUtil.currentStackTrace()); + boolean succ2 = ProjectManagerEx.getInstanceEx().openProject(alienProject); + assertTrue(succ2); + + + try { + PsiManager alienManager = PsiManager.getInstance(alienProject); + final String alienText = "alien"; + + LightVirtualFile alienVirt = new LightVirtualFile("foo.java", alienText); + final PsiFile alienFile = alienManager.findFile(alienVirt); + final PsiDocumentManagerImpl alienDocManager = (PsiDocumentManagerImpl)PsiDocumentManager.getInstance(alienProject); + final Document alienDocument = alienDocManager.getDocument(alienFile); + //alienDocument.putUserData(CACHED_VIEW_PROVIDER, new MockFileViewProvider(alienFile)); + assertEquals(0, alienDocManager.getUncommittedDocuments().length); + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + getPsiDocumentManager() + .documentChanged(new DocumentEventImpl(alienDocument, 0, "", "", alienDocument.getModificationStamp(), false)); + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + assertEquals(0, alienDocManager.getUncommittedDocuments().length); + + alienDocManager.documentChanged(new DocumentEventImpl(alienDocument, 0, "", "", alienDocument.getModificationStamp(), false)); + assertEquals(0, getPsiDocumentManager().getUncommittedDocuments().length); + assertEquals(1, alienDocManager.getUncommittedDocuments().length); + + getPsiDocumentManager().documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + assertEquals(1, getPsiDocumentManager().getUncommittedDocuments().length); + assertEquals(1, alienDocManager.getUncommittedDocuments().length); + + alienDocManager.documentChanged(new DocumentEventImpl(document, 0, "", "", document.getModificationStamp(), false)); + assertEquals(1, getPsiDocumentManager().getUncommittedDocuments().length); + assertEquals(1, alienDocManager.getUncommittedDocuments().length); + } + }); + } + finally { + ProjectUtil.closeAndDispose(alienProject); + } + } + + public void testCommitInBackground() { + PsiFile file = getPsiManager().findFile(createFile()); + assertTrue(file.isPhysical()); + final Document document = getPsiDocumentManager().getDocument(file); + + final Semaphore semaphore = new Semaphore(); + semaphore.down(); + getPsiDocumentManager().performWhenAllCommitted(new Runnable() { + @Override + public void run() { + assertTrue(getPsiDocumentManager().isCommitted(document)); + semaphore.up(); + } + }); + waitAndPump(semaphore); + assertTrue(getPsiDocumentManager().isCommitted(document)); + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + document.insertString(0, "class X {}"); + } + }); + + semaphore.down(); + getPsiDocumentManager().performWhenAllCommitted(new Runnable() { + @Override + public void run() { + assertTrue(getPsiDocumentManager().isCommitted(document)); + semaphore.up(); + } + }); + waitAndPump(semaphore); + assertTrue(getPsiDocumentManager().isCommitted(document)); + + final AtomicInteger count = new AtomicInteger(); + final Runnable action = new Runnable() { + @Override + public void run() { + count.incrementAndGet(); + } + }; + + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + document.insertString(0, "/**/"); + boolean executed = getPsiDocumentManager().cancelAndRunWhenAllCommitted("xxx", action); + assertFalse(executed); + executed = getPsiDocumentManager().cancelAndRunWhenAllCommitted("xxx", action); + assertFalse(executed); + assertEquals(0, count.get()); + } + }); + + while (!getPsiDocumentManager().isCommitted(document)) { + UIUtil.dispatchAllInvocationEvents(); + } + assertTrue(getPsiDocumentManager().isCommitted(document)); + assertEquals(1, count.get()); + + count.set(0); + ApplicationManager.getApplication().runWriteAction(new Runnable() { + public void run() { + document.insertString(0, "/**/"); + boolean executed = getPsiDocumentManager().performWhenAllCommitted(action); + assertFalse(executed); + executed = getPsiDocumentManager().performWhenAllCommitted(action); + assertFalse(executed); + assertEquals(0, count.get()); + } + }); + + while (!getPsiDocumentManager().isCommitted(document)) { + UIUtil.dispatchAllInvocationEvents(); + } + assertTrue(getPsiDocumentManager().isCommitted(document)); + assertEquals(2, count.get()); + } + + private static void waitAndPump(Semaphore semaphore) { + while (!semaphore.waitFor(10)) { + UIUtil.dispatchAllInvocationEvents(); + } + } + + @Override + protected boolean isRunInWriteAction() { + return false; + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java b/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java new file mode 100644 index 000000000000..0dc335947726 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/PsiModificationTrackerTest.java @@ -0,0 +1,137 @@ +package com.intellij.psi; + +import com.intellij.ide.highlighter.JavaFileType; +import com.intellij.openapi.command.WriteCommandAction; +import com.intellij.openapi.editor.SelectionModel; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.testFramework.fixtures.LightPlatformCodeInsightFixtureTestCase; +import com.intellij.util.Processor; +import org.jetbrains.annotations.NonNls; + +import java.io.IOException; + +/** + * @author Dmitry Avdeev + */ +public class PsiModificationTrackerTest extends LightPlatformCodeInsightFixtureTestCase { + public void testAnnotationNotChanged() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + "hi"); + } + + public void testAnnotationNameChanged() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + "hi"); + } + + public void testAnnotationParameterChanged() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + "hi"); + } + + public void testAnnotationRemoved() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + ""); + } + + public void testAnnotationWithClassRemoved() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + ""); + } + + public void testRemoveAnnotatedMethod() throws Exception { + doReplaceTest("public class Foo {\n" + + " " + + " @SuppressWarnings(\"\")\n" + + " public void method() {}\n" + + "" + + "}", + ""); + } + + public void testRenameAnnotatedMethod() throws Exception { + doReplaceTest("public class Foo {\n" + + " @SuppressWarnings(\"\")\n" + + " public void method() {}\n" + + "}", + "zzz"); + } + + public void testRenameAnnotatedClass() throws Exception { + doReplaceTest(" @SuppressWarnings(\"\")\n" + + "public class Foo {\n" + + " public void method() {}\n" + + "}", + "zzz"); + } + + public void testRemoveAll() throws Exception { + doReplaceTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + ""); + } + + public void testRemoveFile() throws Exception { + doTest("@SuppressWarnings(\"zz\")\n" + + "public class Foo { }", + new Processor() { + @Override + public boolean process(PsiFile psiFile) { + try { + final VirtualFile vFile = psiFile.getVirtualFile(); + assert vFile != null : psiFile; + vFile.delete(this); + } + catch (IOException e) { + fail(e.getMessage()); + } + return false; + } + }); + } + + private void doReplaceTest(@NonNls String text, @NonNls final String with) { + doTest(text, new Processor() { + @Override + public boolean process(PsiFile psiFile) { + replaceSelection(with); + return false; + } + }); + } + + private void doTest(@NonNls String text, Processor run) { + PsiFile file = myFixture.configureByText(JavaFileType.INSTANCE, text); + PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker(); + long count = modificationTracker.getModificationCount(); + run.process(file); + assertFalse(modificationTracker.getModificationCount() == count); + } + + private void replaceSelection(final String with) { + new WriteCommandAction.Simple(getProject()) { + @Override + protected void run() throws Throwable { + SelectionModel sel = myFixture.getEditor().getSelectionModel(); + myFixture.getEditor().getDocument().replaceString(sel.getSelectionStart(), sel.getSelectionEnd(), with); + PsiDocumentManager.getInstance(getProject()).commitAllDocuments(); + } + }.execute(); + } + + public void testJavaStructureModificationChangesAfterPackageDelete() { + PsiFile file = myFixture.addFileToProject("/x/y/Z.java", "text"); + PsiModificationTracker modificationTracker = PsiManager.getInstance(getProject()).getModificationTracker(); + long count = modificationTracker.getJavaStructureModificationCount(); + + file.getContainingDirectory().delete(); + + assertEquals(count + 1, modificationTracker.getJavaStructureModificationCount()); + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/psi/SmartPsiElementPointersTest.java new file mode 100644 index 000000000000..bd832266ac46 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/SmartPsiElementPointersTest.java @@ -0,0 +1,360 @@ +package com.intellij.psi; + +import com.intellij.JavaTestUtil; +import com.intellij.codeInsight.CodeInsightTestCase; +import com.intellij.openapi.editor.Document; +import com.intellij.openapi.editor.EditorFactory; +import com.intellij.openapi.editor.event.DocumentEvent; +import com.intellij.openapi.editor.event.DocumentListener; +import com.intellij.openapi.editor.event.EditorEventMulticaster; +import com.intellij.openapi.fileEditor.FileDocumentManager; +import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.projectRoots.impl.JavaSdkImpl; +import com.intellij.openapi.vfs.VfsUtil; +import com.intellij.openapi.vfs.VirtualFile; +import com.intellij.psi.impl.source.PsiFileImpl; +import com.intellij.psi.impl.source.tree.FileElement; +import com.intellij.psi.search.GlobalSearchScope; +import com.intellij.psi.stubs.StubTree; +import com.intellij.psi.util.PsiTreeUtil; +import com.intellij.psi.util.PsiUtil; +import com.intellij.psi.util.PsiUtilBase; +import com.intellij.testFramework.PlatformTestCase; +import com.intellij.testFramework.PsiTestUtil; +import com.intellij.util.FileContentUtil; +import gnu.trove.THashSet; + +import java.io.IOException; +import java.util.Collections; +import java.util.Set; + +@PlatformTestCase.WrapInCommand +public class SmartPsiElementPointersTest extends CodeInsightTestCase { + private VirtualFile myRoot; + + @Override + protected void setUp() throws Exception { + super.setUp(); + + String root = JavaTestUtil.getJavaTestDataPath() + "/codeEditor/smartPsiElementPointers"; + PsiTestUtil.removeAllRoots(myModule, JavaSdkImpl.getMockJdk17()); + myRoot = PsiTestUtil.createTestProjectStructure(myProject, myModule, root, myFilesToDelete); + } + + public void testChangeInDocument() { + PsiClass aClass = myJavaFacade.findClass("AClass", GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + int offset = aClass.getTextOffset(); + document.insertString(offset, "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + // This test is unfair. If pointer would be asked for getElement() between commits it'll never restore again anyway. + // + public void testChangeInDocumentTwice() { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + int offset = aClass.getTextOffset(); + document.insertString(offset, "/*"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + document.insertString(offset + 2, "*/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + public void testGetElementWhenDocumentModified() { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + int offset = aClass.getTextOffset(); + document.insertString(offset, "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + document.insertString(offset, "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"); + + PsiElement element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + public void testKeepBeltWhenDocumentModified() { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + int offset = aClass.getTextOffset(); + document.insertString(offset, "/******/"); + + pointer.getElement(); + + document.insertString(offset, "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + public void testChangeInPsi() { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + int offset = aClass.getTextOffset(); + document.insertString(offset, "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + public void testPsiChangesWithLazyPointers() throws Exception { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + final SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createLazyPointer(aClass.getNameIdentifier()); + final PsiComment javadoc = + JavaPsiFacade.getInstance(aClass.getProject()).getElementFactory().createCommentFromText("/** javadoc */", aClass); + aClass.getParent().addBefore(javadoc, aClass); + + final PsiIdentifier elt = pointer.getElement(); + assertNotNull(elt); + assertSame(elt, aClass.getNameIdentifier()); + } + + public void testTypePointer() { + PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + final PsiTypeElement typeElement = myJavaFacade.findClass("Test",GlobalSearchScope.allScope(getProject())).getFields()[0].getTypeElement(); + + SmartPsiElementPointer typePointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(typeElement); + SmartPsiElementPointer classPointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + + Document aClassDocument = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + Document testDocument = PsiDocumentManager.getInstance(myProject).getDocument(typeElement.getContainingFile()); + + aClassDocument.insertString(aClass.getTextOffset(), "/**/"); + testDocument.insertString(typeElement.getTextOffset(), "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = typePointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiTypeElement); + assertTrue(element.isValid()); + assertEquals(classPointer.getElement(), PsiUtil.resolveClassInType(((PsiTypeElement)element).getType())); + } + + public void testCreatePointerInBeforeDocumentChange() { + final PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + Document document = PsiDocumentManager.getInstance(myProject).getDocument(aClass.getContainingFile()); + + final SmartPsiElementPointer[] pointer = new SmartPsiElementPointer[1]; + int offset = aClass.getTextOffset(); + DocumentListener listener = new DocumentListener() { + @Override + public void beforeDocumentChange(DocumentEvent event) { + pointer[0] = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + } + + @Override + public void documentChanged(DocumentEvent event) { + } + }; + EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); + multicaster.addDocumentListener(listener); + try { + document.insertString(offset, "/******/"); + } + finally { + multicaster.removeDocumentListener(listener); + } + + pointer[0].getElement(); + + document.insertString(0, "/**/"); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + PsiElement element = pointer[0].getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + + public void testCreatePointerWhenNoPsiFile() { + myPsiManager.startBatchFilesProcessingMode(); // to use weak refs + + try { + final PsiClass aClass = myJavaFacade.findClass("AClass",GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + + VirtualFile vFile = myRoot.findChild("AClass.java"); + assertNotNull(vFile); + PsiDocumentManager psiDocumentManager = PsiDocumentManager.getInstance(myProject); + Document document = FileDocumentManager.getInstance().getDocument(vFile); + + final SmartPsiElementPointer pointer = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + + System.gc(); + /* + PsiFile psiFile = myPsiManager.getFileManager().getCachedPsiFile(vFile); + assertNull(psiFile); + */ + + document.insertString(0, "class Foo{}\n"); + + PsiElement element = pointer.getElement(); + assertEquals(aClass, element); + + document.insertString(0, "/**/"); + psiDocumentManager.commitAllDocuments(); + + if (aClass.isValid()) { + aClass.getChildren(); + } + + element = pointer.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + } + finally { + myPsiManager.finishBatchFilesProcessingMode(); // to use weak refs + } + } + + public void testReplaceFile() throws IOException { + VirtualFile vfile = myRoot.createChildData(this, "X.java"); + VfsUtil.saveText(vfile, "public class X { public int X; }"); + + PsiClass aClass = myJavaFacade.findClass("X", GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + assertTrue(aClass.isValid()); + + SmartPsiElementPointer classp = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + SmartPsiElementPointer filep = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass.getContainingFile()); + + FileContentUtil.reparseFiles(myProject, Collections.singleton(vfile), true); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + assertFalse(aClass.isValid()); + + PsiElement element = classp.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiClass); + assertTrue(element.isValid()); + assertEquals(vfile, element.getContainingFile().getVirtualFile()); + + element = filep.getElement(); + assertNotNull(element); + assertTrue(element instanceof PsiFile); + assertTrue(element.isValid()); + assertEquals(vfile, element.getContainingFile().getVirtualFile()); + } + + public void testCreatePointerDoesNotLoadPsiTree() throws IOException { + VirtualFile vfile = myRoot.createChildData(this, "X.java"); + VfsUtil.saveText(vfile, "public class X { public int X; }"); + + PsiClass aClass = myJavaFacade.findClass("X", GlobalSearchScope.allScope(getProject())); + assertNotNull(aClass); + assertTrue(aClass.isValid()); + + PsiFileImpl file = (PsiFileImpl)aClass.getContainingFile(); + + assertTreeLoaded(file, false); + + SmartPsiElementPointer p = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(aClass); + assertNotNull(p); + + assertTreeLoaded(file, false); + + assertInstanceOf(p.getElement(), PsiClass.class); + + assertTreeLoaded(file, false); + + PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); + Document document = documentManager.getDocument(file); + document.insertString(0, "/** asdasd */"); + documentManager.commitAllDocuments(); + + // loaded tree + assertTreeLoaded(file, true); + + assertInstanceOf(p.getElement(), PsiClass.class); + + assertTreeLoaded(file, true); + } + + private static void assertTreeLoaded(PsiFileImpl file, boolean loaded) { + FileElement treeElement = file.getTreeElement(); + assertEquals(loaded, treeElement != null); + StubTree stubTree = file.getStubTree(); + assertEquals(loaded, stubTree == null); + } + + public void testPointerDisambiguationAfterDupLine() throws Exception { + PsiJavaFile file = (PsiJavaFile)configureByText(StdFileTypes.JAVA, "class XXX{ void foo() { \n" + + " foo();\n" + + "}}"); + PsiClass aClass = file.getClasses()[0]; + assertNotNull(aClass); + + PsiReferenceExpression ref1 = PsiTreeUtil.getParentOfType(PsiUtilBase.getElementAtCaret(getEditor()), PsiReferenceExpression.class); + SmartPsiElementPointer pointer1 = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(ref1); + + ctrlD(); + PsiDocumentManager.getInstance(myProject).commitAllDocuments(); + + Set refs = new THashSet(); + int offset=0; + while (true) { + offset = getEditor().getDocument().getText().indexOf("foo();", offset+1); + if (offset == -1) break; + PsiReferenceExpression ref2 = PsiTreeUtil.getParentOfType(getFile().findElementAt(offset), PsiReferenceExpression.class); + refs.add(ref2); + } + refs.remove(ref1); + assertEquals(1, refs.size()); + PsiReferenceExpression ref2 = refs.iterator().next(); + assertNotSame(ref1, ref2); + SmartPsiElementPointer pointer2 = SmartPointerManager.getInstance(myProject).createSmartPsiElementPointer(ref2); + assertNotSame(pointer1, pointer2); + + PsiElement element1 = pointer1.getElement(); + PsiElement element2 = pointer2.getElement(); + + assertNotNull(element1); + assertNotNull(element2); + assertNotSame(element1, element2); + + assertFalse(SmartPointerManager.getInstance(myProject).pointToTheSameElement(pointer1, pointer2)); + } +} diff --git a/java/java-tests/testSrc/com/intellij/psi/TreeIsCorrectAfterDiffReparseTest.java b/java/java-tests/testSrc/com/intellij/psi/TreeIsCorrectAfterDiffReparseTest.java new file mode 100644 index 000000000000..b5e1712fbbd6 --- /dev/null +++ b/java/java-tests/testSrc/com/intellij/psi/TreeIsCorrectAfterDiffReparseTest.java @@ -0,0 +1,1097 @@ +/* + * @author max + */ +package com.intellij.psi; + +import com.intellij.openapi.editor.Document; +import com.intellij.psi.impl.DebugUtil; +import com.intellij.testFramework.LightCodeInsightTestCase; + +public class TreeIsCorrectAfterDiffReparseTest extends LightCodeInsightTestCase { + + public void testIDEADEV41862() throws Exception { + String part1 = "package com.test;\n" + + "\n" + + "\n" + + "//------------------------------------------------------------------\n" + + "// Copyright (c) 1999, 2007\n" + + "// WorkForce Software, Inc.\n" + + "// All rights reserved.\n" + + "//\n" + + "// Web-site: http://www.workforcesoftware.com\n" + + "// E-mail: support@workforcesoftware.com\n" + + "// Phone: (877) 493-6723\n" + + "//\n" + + "// This program is protected by copyright laws and is considered\n" + + "// a trade secret of WorkForce Software. Access to this program\n" + + "// and source code is granted only to licensed customers. Under\n" + + "// no circumstances may this software or source code be distributed\n" + + "// without the prior written consent of WorkForce Software.\n" + + "// -----------------------------------------------------------------\n" + + "\n" + + "import com.workforcesoftware.Data.Employee.*;\n" + + "import com.workforcesoftware.Data.*;\n" + + "import com.workforcesoftware.Data.assignment.Asgnmt_master;\n" + + "import com.workforcesoftware.Data.assignment.Asgnmt;\n" + + "import com.workforcesoftware.Data.Output.Time_sheet_output;\n" + + "import com.workforcesoftware.Data.TimeSched.PayPeriodData;\n" + + "import com.workforcesoftware.Data.TimeSched.TimeSchedUtils;\n" + + "import com.workforcesoftware.Data.TimeSched.Schedule.Schedule_detailList;\n" + + "import com.workforcesoftware.Data.TimeSched.TimeSheet.*;\n" + + "import com.workforcesoftware.Data.assignment.Asgnmt_masterList;\n" + + "import com.workforcesoftware.Exceptions.*;\n" + + "import com.workforcesoftware.Gen.Choice.Approval_event_type;\n" + + "import com.workforcesoftware.Gen.Choice.Transaction_status;\n" + + "import com.workforcesoftware.Gen.Choice.Messages;\n" + + "import com.workforcesoftware.Gen.Choice.Program_source;\n" + + "import com.workforcesoftware.Gen.Other.DbRec.DbRecTime_sheet_output;\n" + + "import com.workforcesoftware.Gen.Other.DbRec.DbRecTime_sheet;\n" + + "import com.workforcesoftware.Gen.Other.List.WDateList;\n" + + "import com.workforcesoftware.Gen.Policy.Right_grp;\n" + + "import com.workforcesoftware.Gen.Policy.Policy_profile;\n" + + "import com.workforcesoftware.Policy.*;\n" + + "import com.workforcesoftware.Util.DateTime.WDate;\n" + + "import com.workforcesoftware.Util.DateTime.WDateTime;\n" + + "import com.workforcesoftware.Util.DB.ListWriter;\n" + + "import com.workforcesoftware.Util.DB.DbRecFieldCopier;\n" + + "import com.workforcesoftware.ClientRequests.TimeSched.TimeSchedUtil;\n" + + "import com.workforcesoftware.ClientRequests.TimeSched.PayPeriodInfo;\n" + + "import com.workforcesoftware.ClientRequests.TimeEntry.ApprovalInfo;\n" + + "import com.workforcesoftware.ClientRequests.TimeEntry.BankBalanceResults;\n" + + "import com.workforcesoftware.Misc.ServerErrorLogger;\n" + + "import com.workforcesoftware.Misc.ServerError;\n" + + "import com.workforcesoftware.AssignmentPeriod.*;\n" + + "import com.workforcesoftware.AssignmentPeriod.TimeSheetState;\n" + + "import com.workforcesoftware.Dictionary.DataDictionary;\n" + + "\n" + + "import java.sql.SQLException;\n" + + "import java.util.*;\n" + + "import org.log4j.Category;\n" + + "\n" + + "/**\n" + + " * Holds definition of {@link CalcInfo}\n" + + " */\n" + + "class AssignmentManager {\n" + + " /**\n" + + " * Implementation of {@link TimeSheetCalculationInfo}.\n" + + " * Extracts all the required data from AllCalcDataManager, AllCalculationData, Time_sheet, etc.\n" + + " * Makes copies of all mutable data, unmodifiable lists where possible.\n" + + " */\n" + + " static class CalcInfo implements TimeSheetCalculationInfo {\n" + + " /**\n" + + " * Use the passed in AllCalculationData object - DO NOT try to get it from the passed in AllCalcDataManager because\n" + + " * the ACD object that's passed in is obtained from AssignmentManager.getAllCalcData method which handles the\n" + + " * scenario where a prior period may need to be amended automatically and if ACDM.getAllCalcData() method is called,\n" + + " * you might get an unmodifiable ACD\n" + + " * @param acdm\n" + + " * @param acd\n" + + " * @param timeSheetId\n" + + " * @param parms\n" + + " */\n" + + " CalcInfo(AllCalcDataManager acdm, AllCalculationData acd, TimeSheetIdentifier timeSheetId, TimeEntryParms parms) {\n" + + " // we'll extract all these vars separately, for readability\n" + + " final Right_grp rightGrp = parms.getRight_grp(timeSheetId);\n" + + " final AllPolicies ap = acd.getAllPolicies();\n" + + " final WDate ppEndForToday = TimeSchedUtil.getPayPeriodRangeForToday(acdm).getEnd();\n" + + "\n" + + " this.acd = acd;\n" + + " this.timeSheetIdentifier = timeSheetId;\n" + + " this.approval_eventList = acd.getApproval_eventList().getUnmodifiableList();\n" + + " this.policyProfile = acd.getAsgnmtMaster().getPolicyProfilePolicy(ppEndForToday, ap);\n" + + " this.adjustmentsPaidWithTimesheet = createAdjustmentsPaidWithTimesheet(acdm, acd);\n" + + " this.bankBalanceResults = acdm.getBankDataForBankBalancePreview(ap, acd, rightGrp.getRight_grp(), acd.getPP_end());\n" + + " this.tsoListForPayPreview = Collections.unmodifiableList(acdm.getTsoListForPayPreview(acd, rightGrp.getRight_grp()));\n" + + " this.approvedDays = createApprovedDays(acd);\n" + + "\n" + + " try {\n" + + " // these are fairly involved calculations, and we want to validate the pay period list,\n" + + " // so we'll do them both here together instead of upon request\n" + + " employeePeriodInfo = acdm.getEmployeePeriodInfo(timeSheetId.getPpEnd());\n" + + " payPeriodList = TimeSchedUtil.calcPayPeriodList(acdm, rightGrp.getRight_grp());\n" + + " }\n" + + " catch (Exception e) {\n" + + " throw new InternalApplicationException(\"Loading calc data for: \" + timeSheetId, e);\n" + + " }\n" + + " assertPayPeriodList();\n" + + "\n" + + " AssignmentPeriodStateImpl aps = new AssignmentPeriodStateImpl(employeePeriodInfo,\n" + + " rightGrp, parms.getSystemFeature(), ap, ppEndForToday, acdm, policyProfile);\n" + + " assignmentPeriodState = aps;\n" + + " timeSheetState = new TimeSheetStateImpl(this, aps, policyProfile);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns an ApprovalInfo object, which contains information about whether\n" + + " * employee/manager approved a timesheet.\n" + + " *\n" + + " * @see ApprovalInfo\n" + + " */\n" + + " public ApprovalInfo getApprovalInfo() { return new ApprovalInfo(acd.getTime_sheet(), approval_eventList); }\n" + + "\n" + + " public Collection getAdjustmentsPaidWithTimesheet() {\n" + + " return adjustmentsPaidWithTimesheet;\n" + + " }\n" + + "\n" + + " public Approval_eventList getApproval_eventList() {\n" + + " return approval_eventList;\n" + + " }\n" + + "\n" + + "\n" + + " /**\n" + + " * Returns state information about a given assignment and period, encompassing\n" + + " * the cross-section of security related settings calculated from:\n" + + " *
    \n" + + " *
  • App_user Roles (app_user_right and right_grp tables)\n" + + " *
  • Assignment effective dates\n" + + " *
  • Employee record effective dates\n" + + " *
  • System_feature read/write privileges\n" + + " *
\n" + + " *\n" + + " * @return not ever null\n" + + " */\n" + + " public AssignmentPeriodState getAssignmentPeriodState() {\n" + + " return assignmentPeriodState;\n" + + " }\n" + + "\n" + + " public Set getDailyApprovalDays() {\n" + + " return approvedDays;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns map to the accrual banks data, which is a read only reference\n" + + " * of the information contained there. The banks returned will be all banks attached\n" + + " * to the assignment, including aggregate banks if applicable.\n" + + " *\n" + + " * The list will not contain any banks that the current App_user's Right_grp cannot view for this assignment's\n" + + " * Policy_profile (as defined by {@link com.workforcesoftware.Gen.Policy.User_entry_rule_detail#getDisplay_bank_set()})\n" + + " */\n" + + " public List getBankBalanceResults() {\n" + + " return bankBalanceResults;\n" + + " }\n" + + "\n" + + " public Approval_event getLastValidEmployeeApproveEvent() {\n" + + " return approval_eventList.getLastValidEmployeeApproveEvent();\n" + + " }\n" + + "\n" + + " public Time_sheet_detail_splitList getTime_sheet_detail_splitList() {\n" + + " return acd.getTime_sheet().getTime_sheet_detail_splitList().getUnmodifiableList();\n" + + " }\n" + + "\n" + + " public Time_sheet_detailList getTime_sheet_detailList() {\n" + + " return acd.getTime_sheet().getDetailList().getUnmodifiableList();\n" + + " }\n" + + "\n" + + " public Schedule_detailList getSchedule_detailList() {\n" + + " return acd.getSchedule().getDetailList().getUnmodifiableList();\n" + + " }\n" + + "\n" + + " public Time_sheet_exceptionList getTime_sheet_exceptionList() {\n" + + " return acd.getTime_sheet().getTime_sheet_exceptionList().getUnmodifiableList();\n" + + " }\n" + + "\n" + + " public List getTsoListForPayPreview() {\n" + + " return tsoListForPayPreview;\n" + + " }\n" + + "\n" + + " public Approval_eventList getValidManagerApproveEvents() {\n" + + " return approval_eventList.getValidManagerApproveEvents();\n" + + " }\n" + + "\n" + + " public TimeSheetIdentifier getTimeSheetIdentifier() {\n" + + " return timeSheetIdentifier;\n" + + " }\n" + + "\n" + + " public EmployeePeriodInfo getEmployeePeriodInfo() {\n" + + " return employeePeriodInfo;\n" + + " }\n" + + "\n" + + " public Employee_master getEmployeeMaster() {\n" + + " return new Employee_master(acd.getEmployee_master());\n" + + " }\n" + + "\n" + + " public Employee getEmployee() {\n" + + " return new Employee(acd.getEmployee_master().getEmployeeAsOfOrAfter(getPPEnd()));\n" + + " }\n" + + "\n" + + " public Asgnmt_master getAsgnmtMaster() {\n" + + " return new Asgnmt_master(acd.getAsgnmtMaster());\n" + + " }\n" + + "\n" + + " public Asgnmt getAsgnmt() {\n" + + " return new Asgnmt(acd.getAsgnmt());\n" + + " }\n" + + "\n" + + " public WDate getPPEnd() {\n" + + " return employeePeriodInfo.getPp_end();\n" + + " }\n" + + "\n" + + " public WDate getPPBegin() {\n" + + " return employeePeriodInfo.getPp_begin();\n" + + " }\n" + + "\n" + + " public Policy_profile getPolicy_profile() {\n" + + " return policyProfile;\n" + + " }\n" + + "\n" + + "\n" + + " /**\n" + + " * Return state information about the timesheet, which considers security\n" + + " * relationship between the current state (locked/closed/amended) in relation to:\n" + + " *
    \n" + + " *
  • App_user Roles (app_user_right and right_grp tables)\n" + + " *
  • Assignment effective dates\n" + + " *
  • Employee record effective dates\n" + + " *
  • System_feature read/write privileges\n" + + " *
  • Approval level of the timesheet\n" + + " *
\n" + + " *\n" + + " * @return not ever null\n" + + " */\n" + + " public TimeSheetState getTimeSheetState() {\n" + + " return timeSheetState;\n" + + " }\n" + + "\n" + + " public int getVersionNumber() {\n" + + " return timeSheetIdentifier.getVersion();\n" + + " }\n" + + "\n" + + " public PayPeriodData getPayPeriodData() {\n" + + " return acd.getPayPeriodData();\n" + + " }\n" + + "\n" + + " public boolean getIsNewlyUserAmended() {\n" + + " return acd.isNewlyUserAmended();\n" + + " }\n" + + "\n" + + " public List getPayPeriodList() {\n" + + " return payPeriodList;\n" + + " }\n" + + "\n" + + " private static Collection createAdjustmentsPaidWithTimesheet(AllCalcDataManager acdm, AllCalculationData acd) {\n" + + " try { return Collections.unmodifiableCollection(acdm.getAdjustmentsPaidWithTimesheet(acd)); }\n" + + " catch (SQLException e) { throw new InternalApplicationException(\"Unexpected SQL Exception\", e); }\n" + + " }\n" + + "\n" + + " private void assertPayPeriodList() {\n" + + " //TODO - probably need to show a \"nice\" message to the user saying that the employee doesn't\n" + + " //TODO - have a viewable/editable pay period.\n" + + " if(payPeriodList.isEmpty()) {\n" + + " throw new InternalApplicationException(\"No viewable/editable pay periods found for employee \"\n" + + " + employeePeriodInfo.getEmployeeMaster().getEmployee());\n" + + " }\n" + + " }\n" + + "\n" + + " private static Set createApprovedDays(AllCalculationData acd) {\n" + + " // calculate days approved by daily approvals\n" + + " Set approvedDays = new HashSet();\n" + + " for( WDate date : acd.getPayPeriodData().getActiveDates().getAllDates() ) {\n" + + " if( acd.isDayApproved(date) ) { approvedDays.add(date); }\n" + + " }\n" + + " return Collections.unmodifiableSet(approvedDays);\n" + + " }\n" + + "\n" + + " private final Collection adjustmentsPaidWithTimesheet;\n" + + " private final Approval_eventList approval_eventList;\n" + + " private final AssignmentPeriodState assignmentPeriodState;\n" + + " private final List bankBalanceResults;\n" + + " private final List tsoListForPayPreview;\n" + + " private final TimeSheetIdentifier timeSheetIdentifier;\n" + + " private final TimeSheetState timeSheetState;\n" + + " private final EmployeePeriodInfo employeePeriodInfo;\n" + + " private final ArrayList payPeriodList;\n" + + " private final Set approvedDays;\n" + + " private final Policy_profile policyProfile;\n" + + " private final AllCalculationData acd;\n" + + " }\n" + + "\n" + + " AssignmentManager(GeneratedId assignmentId, Set timeSheetIdentifiers, TimeEntryParmsPerAssignment parms) {\n" + + " this.asgnmtMaster = getAggregateOrSingleAssignmentMaster(assignmentId);\n" + + " setClassFields(parms, timeSheetIdentifiers);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Obtains the single or aggregate assignment master for the given assignmentId. If the given assignmentId is not a\n" + + " * single or aggregate assignment, fetches the aggregate associated with the given assignmentId and returns that.\n" + + " *\n" + + " * @param assignmentId Id of the assigment to load.\n" + + " * @return the single or aggregate assignment master for the given assignmentId.\n" + + " */\n" + + " private static Asgnmt_master getAggregateOrSingleAssignmentMaster(GeneratedId assignmentId) {\n" + + " Asgnmt_master am = Asgnmt_master.load(assignmentId);\n" + + " if(am.isAggregate() || am.isSingle()) {\n" + + " return am;\n" + + " }\n" + + "\n" + + " //If it's not a single or aggregate, we need to load the aggregate and load that.\n" + + " return Asgnmt_master.load(am.getAggregate_asgnmt());\n" + + " }\n" + + "\n" + + " AggregateTimeEntryTransactionResults load() {\n" + + " return loadImpl();\n" + + " }\n" + + "\n" + + " /**\n" + + " * Reverts any changes that have not been committed to the database on this AssignmentManager\n" + + " */\n" + + " void revert() {\n" + + " updatedAllCalcDataManager = null;\n" + + " }\n" + + "\n" + + " /**\n" + + " * @return Return initial transaction results from originalAllCalcData, which must be set before calling this.\n" + + " */\n" + + " private AggregateTimeEntryTransactionResults loadImpl() {\n" + + " AggregateTimeEntryTransactionResults aggregateResults = (AggregateTimeEntryTransactionResults) parms.getTimeEntryResultsFactory().newInstance();\n" + + " for (TimeSheetIdentifier timeSheetId : getTimeSheetIdentifiers()) {\n" + + " AllCalculationData acd = getOriginalAllCalcData(timeSheetId);\n" + + " // Make sure system_record_id's are assigned - TimeEntryManager and client do not function correctly without\n" + + " assignSystemRecordIds(acd);\n" + + " CalcInfo calcInfo = new CalcInfo(getOriginalAllCalcDataManager(timeSheetId.getAsgnmt()), acd, timeSheetId, parms);\n" + + " //todo-ute-nazim do we really need to perform TimeSheetDiff for the initial load ?\n" + + " TimeEntryTransactionResults singleTimeSheetResults =\n" + + " new TimeSheetDiff(timeSheetId, acd, calcInfo, TimeSheetCounters.getEmptyCounters(), NewToOldIdMap.EMPTY,\n" + + " Collections.EMPTY_LIST, new StatusMapsForTimesheetAndSchedule(), true, parms, null);\n" + + " aggregateResults.add(singleTimeSheetResults);\n" + + " }\n" + + " return aggregateResults;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Assign system_record_id's for time_sheet, time_sheet_detail's and time_sheet_exception's\n" + + " * @param acd\n" + + " */\n" + + " private void assignSystemRecordIds(AllCalculationData acd) {\n" + + " assignSystemRecordId(acd.getTime_sheet());\n" + + " assignSystemRecordIds(acd.getTime_sheet().getTime_sheet_detailList());\n" + + " assignSystemRecordIds(acd.getTime_sheet().getTime_sheet_exceptionList());\n" + + " }\n" + + "\n" + + " /**\n" + + " * todo: store the event.getComment()\n" + + " * @param event\n" + + " */\n" + + " AggregateTimeEntryTransactionResults amend(TransactionApprovalEvent event) {\n" + + " TimeSheetIdentifier singleTimeSheetId = event.getTimeSheetIdentifier();\n" + + " //TODO: Shouldn't this call createOriginalAllCalcDataManagerIfNeeded? Seems like we needlessly reload the original\n" + + " //TODO: ACDM. This is because a timesheet which was prepared for amendment couldn't have been modified before it\n" + + " //TODO: was amended.\n" + + " invalidateAllCalcDataManagers();\n" + + "\n" + + " // Create the amended ACD and put into the ACDM cache\n" + + " EmployeePeriodInfo epInfo = null;\n" + + " try {\n" + + " AllCalcDataManager acdm = getOriginalAllCalcDataManager(singleTimeSheetId.getAsgnmt());\n" + + " epInfo = acdm.getEmployeePeriodInfo(singleTimeSheetId.getPpEnd());\n" + + " AllCalculationData amendedAllCalcData = acdm.createAmendedAllCalcData(epInfo, parms.getApp_user(), WDateTime.now());\n" + + " // No need to hook this up to approval change logic, since it is an in-memory change so far.\n" + + " //Recalculate the amended ACD object so that timesheet exceptions that are on the closed version (if any)\n" + + " // get created on the amended timesheet as well\n" + + " amendedAllCalcData.recalc(acdm);\n" + + " }\n" + + " catch (SQLException e) {\n" + + " throw new InternalApplicationException(\"amending time sheet for \" + singleTimeSheetId, e);\n" + + " }\n" + + " catch (MultipleRowDbRecException e) {\n" + + " throw new InternalApplicationException(\"amending time sheet for \" + singleTimeSheetId, e);\n" + + " } catch (Exception e) {\n" + + " throw new InternalApplicationException(\"amending time sheet for \" + singleTimeSheetId, e);\n" + + " }\n" + + " return loadImpl();\n" + + " }\n" + + "\n" + + " TimeSheetTransactionApplier applyTransaction(TimeSheetIdentifier id, TimeEntryTransaction trans) {\n" + + " AllCalcDataManager updatedAcdm = getUpdatedAllCalcDataManager(id.getAsgnmt());\n" + + " AllCalculationData acd = getAllCalcData(updatedAcdm, id);\n" + + " return new TimeSheetTransactionApplier(trans, updatedAcdm, acd, parms, id);\n" + + " }\n" + + "\n" + + " private void recalc(TimeSheetIdentifier timeSheetId, Approval_event_type approvalEventType) {\n" + + " AllCalcDataManager updatedAcdm = getUpdatedAllCalcDataManager(timeSheetId.getAsgnmt());\n" + + " AllCalculationData acd = getAllCalcData(updatedAcdm, timeSheetId);\n" + + " try {\n" + + " if (approvalEventType == Approval_event_type.SAVE_SCHEDULE) {\n" + + " // if we're saving a change to the schedule, the timesheet might need to be re-initialized.\n" + + " acd.reinitTimeSheet(updatedAcdm, true);\n" + + " }\n" + + " acd.recalc(updatedAcdm);\n" + + " }\n" + + " catch (Exception e) {\n" + + " throw new InternalApplicationException(\"calculate for \" + timeSheetId, e);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Save one timesheet to a ListWriter using AllCalculationData.\n" + + " * @param timeSheetId\n" + + " * @param saveEvent\n" + + " * @throws Exception\n" + + " */\n" + + " void save(TimeSheetIdentifier timeSheetId, Approval_event_type saveEvent) throws Exception {\n" + + " GeneratedId asgnmtId = timeSheetId.getAsgnmt();\n" + + " AllCalcDataManager acdMgr = getUpdatedAllCalcDataManager(asgnmtId);\n" + + " AllCalculationData acd = getAllCalcData(acdMgr, timeSheetId);\n" + + "\n" + + " // TODO: it'd be nice if we were able to retry a save if it failed because of unrelated changes (to accomodate for\n" + + " // concurrent save operations on other component timesheets besides the one that the user is trying to save)\n" + + " final ListWriter lw = new ListWriter();\n" + + " acd.saveAfterApplyingApprovalEvent(acdMgr, saveEvent, lw);\n" + + "\n" + + " //The exceptions must be generated after the ACD has performed approval related operations.\n" + + " // Loading the original ACD to get the timesheet object before the approval. This will be used to determine\n" + + " // timesheet's approval level before this save event.\n" + + " AllCalculationData originalAcd = getAllCalcData(getOriginalAllCalcDataManager(asgnmtId), timeSheetId);\n" + + " acdMgr.generateExceptionNotifications(acd, originalAcd.getTime_sheet(), acd.getTime_sheet(), lw);\n" + + " lw.writeLists();\n" + + "\n" + + " }\n" + + "\n" + + " /**\n" + + " * If time sheets were saved and written to the database successfully, update internal data\n" + + " * to reflect what's in the database as the new \"original\" data.\n" + + " */\n" + + " void postSave() {\n" + + " originalAllCalcDataManager = updatedAllCalcDataManager;\n" + + " updatedAllCalcDataManager = null;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns true if the given timesheet contains exceptions that should prevent save.\n" + + " * @param timeSheetId time sheet ID\n" + + " * @return true if exceptions exist that should prevent save, false otherwise.\n" + + " */\n" + + " boolean hasExceptionsPreventingSave(TimeSheetIdentifier timeSheetId) {\n" + + " AllCalculationData acd = getUpdatedAllCalcData(timeSheetId);\n" + + " try {\n" + + " return acd.getTime_sheet().getTime_sheet_exceptionList().getDisallow_timesheet_save(acd.getAllPolicies());\n" + + " }\n" + + " catch (PolicyLookupRequiredException e) {\n" + + " throw new InternalApplicationException(\"Policy error on timesheet \" + timeSheetId);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns true if the given timesheet contains exceptions that should prevent submit.\n" + + " * @param timeSheetId time sheet ID\n" + + " * @return true if exceptions exist that should prevent submit, false otherwise.\n" + + " */\n" + + " boolean hasExceptionsPreventingSubmit(TimeSheetIdentifier timeSheetId) {\n" + + " AllCalculationData acd = getUpdatedAllCalcData(timeSheetId);\n" + + " try {\n" + + " return acd.getTime_sheet().getTime_sheet_exceptionList().getDisallow_timesheet_submit(acd.getAllPolicies());\n" + + " }\n" + + " catch (PolicyLookupRequiredException e) {\n" + + " throw new InternalApplicationException(\"Policy error on timesheet \" + timeSheetId);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Compute {@link TimeEntryTransactionResults} changes to be returned in client.\n" + + " * @param timeSheetId\n" + + " * @param trans transaction which client just sent\n" + + " * @param applyResults results (errors) of applying the transaction\n" + + " * @param timeSheetSavedStatus a status to be added representing the save results - SAVED or ERROR, or null if none.\n" + + " * @param transactionSuccessful value for {@link TimeEntryTransactionResults#getTransactionWasSuccessful}.\n" + + " */\n" + + " TimeSheetDiff getDiff(TimeSheetIdentifier timeSheetId, TimeEntryTransaction trans,\n" + + " TimeSheetTransactionApplier applyResults, TransactionStatus timeSheetSavedStatus,\n" + + " boolean transactionSuccessful)\n" + + " {\n" + + " AllCalcDataManager acdm = getLatestAllCalcDataManager(timeSheetId.getAsgnmt());\n" + + " AllCalculationData acd = getAllCalcData(acdm, timeSheetId);\n" + + " NewToOldIdMap newToOldIdMap = applyResults.getNewToOldIdMap();\n" + + " StatusMapsForTimesheetAndSchedule timeSheetRowStatusMap = applyResults.getTimeEntryRowErrorMap();\n" + + "\n" + + " if (cat.isDebugEnabled()) {\n" + + " ObjectDumper.debug(cat, \"TimeSheetDiff getDiff before\", timeSheetRowStatusMap, null, true);\n" + + " }\n" + + "\n" + + " List timeSheetStatuses = new ArrayList(applyResults.getTimeSheetErrors());\n" + + " TimeSheetCounters counters = new TimeSheetCounters(trans, timeSheetId, newToOldIdMap);\n" + + " if (timeSheetSavedStatus != null) {\n" + + " timeSheetStatuses.add(timeSheetSavedStatus);\n" + + " if (timeSheetSavedStatus.getTransaction_status() == Transaction_status.SAVED ||\n" + + " timeSheetSavedStatus.getTransaction_status() == Transaction_status.ERROR) {\n" + + " // if we saved or there is an error mark every TimeEntryRow in the transaction as such.\n" + + " // get all row id's from the transaction (new id's - not temp id's)\n" + + " Collection transTimeSheetRowIds = applyResults.getTransactionTimeSheetRowIds();\n" + + " Collection transScheduleRowIds = applyResults.getTransactionScheduleRowIds();\n" + + " timeSheetRowStatusMap = new StatusMapsForTimesheetAndSchedule(transTimeSheetRowIds, transScheduleRowIds, timeSheetSavedStatus);\n" + + " timeSheetRowStatusMap.addAll(applyResults.getTimeEntryRowErrorMap());\n" + + " }\n" + + " }\n" + + "\n" + + " if (cat.isDebugEnabled()) {\n" + + " ObjectDumper.debug(cat, \"TimeSheetDiff getDiff after\", timeSheetRowStatusMap, null, true);\n" + + " }\n" + + "\n" + + " CalcInfo calcInfo = new CalcInfo(acdm, acd, timeSheetId, parms);\n" + + " return new TimeSheetDiff(timeSheetId, acd, calcInfo, counters, newToOldIdMap, timeSheetStatuses,\n" + + " timeSheetRowStatusMap, transactionSuccessful, parms, trans);\n" + + " }\n" + + "\n" + + " TimeSheetCalculationInfo getCalculationInfo(TimeSheetIdentifier timeSheetIdentifier) {\n" + + " validate(timeSheetIdentifier);\n" + + " final AllCalcDataManager acdm = getLatestAllCalcDataManager(timeSheetIdentifier.getAsgnmt());\n" + + " return new CalcInfo(acdm, getAllCalcData(acdm, timeSheetIdentifier), timeSheetIdentifier, parms);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Return the calculation info without auto-amending\n" + + " * @param acdm\n" + + " * @param timeSheetIdentifier\n" + + " * @return\n" + + " */\n" + + " private TimeSheetCalculationInfo getCalculationInfo(AllCalcDataManager acdm, TimeSheetIdentifier timeSheetIdentifier) {\n" + + " validate(timeSheetIdentifier);\n" + + " try {\n" + + " return new CalcInfo(acdm, acdm.getAllCalcData(timeSheetIdentifier.getPpEnd(), timeSheetIdentifier.getVersion()), timeSheetIdentifier, parms);\n" + + " } catch (Exception e) {\n" + + " throw new InternalApplicationException(\"Error creating CalcInfo \", e); \n" + + " }\n" + + " }\n" + + "\n" + + " private void invalidateAllCalcDataManagers() {\n" + + " originalAllCalcDataManager = null;\n" + + " updatedAllCalcDataManager = null;\n" + + " }\n" + + "\n" + + " private void createUpdatedAllCalcDataManager(Set timeSheetIds) {\n" + + " AllCalcDataManager origAcdMgr = getOriginalAllCalcDataManager(asgnmtMaster.getAsgnmt());\n" + + " try {\n" + + " WDateList dateList = new WDateList();\n" + + " for(TimeSheetIdentifier id : timeSheetIds) {\n" + + " dateList.add(id.getPpEnd());\n" + + " }\n" + + " updatedAllCalcDataManager = origAcdMgr.copyForUpdate(dateList);\n" + + " } catch (Exception e) {\n" + + " throw new InternalApplicationException(\"Cloning for \" + timeSheetIds, e);\n" + + " }\n" + + " }\n" + + "\n" + + " private void validate(TimeSheetIdentifier timeSheetId) throws IllegalArgumentException {\n" + + " if (!timeSheetIdentifiers.contains(timeSheetId)) {\n" + + " throw new IllegalArgumentException(\"Invalid time sheet specified: \" + timeSheetId\n" + + " + \". not in \" + timeSheetIdentifiers);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Obtains the original AllCalcDataManager for the given assignment Id. The assignment id must be given because in a\n" + + " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" + + " * to the aggregate assignment.\n" + + " *\n" + + " * @param asgnmtId The id of the assignment to load.\n" + + " * @return the original AllCalcDataManager for the given assignment Id\n" + + " */\n" + + " private AllCalcDataManager getOriginalAllCalcDataManager(GeneratedId asgnmtId) {\n" + + " if(originalAllCalcDataManager == null) {\n" + + " originalAllCalcDataManager = createOriginalAllCalcDataManager();\n" + + " updatedAllCalcDataManager = null;\n" + + " }\n" + + "\n" + + " if(asgnmtId.equals(originalAllCalcDataManager.getAsgnmtId())) {\n" + + " return originalAllCalcDataManager;\n" + + " }\n" + + "\n" + + " return originalAllCalcDataManager.getAllCalcDataManager(asgnmtId);\n" + + " }\n" + + "\n" + + " private AllCalcDataManager createOriginalAllCalcDataManager() {\n" + + " AllPolicies allPolicies = PolicyManager.getInstance().getAllPolicies();\n" + + " try {\n" + + " return AllCalcDataManager.createForAsgnmt(asgnmtMaster, allPolicies);\n" + + " }\n" + + " catch (Exception e) {\n" + + " throw new InternalApplicationException(\"Failed to load ACDM for assignment with id:\"+asgnmtMaster.getAsgnmt(), e);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Obtains the updated AllCalcDataManager for the given assignment Id. The assignment id must be given because in a\n" + + " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" + + " * to the aggregate assignment.\n" + + " *\n" + + " * @param asgnmtId The id of the assignment to load.\n" + + " * @return the updated AllCalcDataManager for the given assignment Id\n" + + " */\n" + + " private AllCalcDataManager getUpdatedAllCalcDataManager(GeneratedId asgnmtId) {\n" + + " if(updatedAllCalcDataManager == null) {\n" + + " throw new InternalApplicationException(\"Attempted to use the updated all calc data manager before it was created.\");\n" + + " }\n" + + "\n" + + " if(asgnmtId.equals(updatedAllCalcDataManager.getAsgnmtId())) {\n" + + " return updatedAllCalcDataManager;\n" + + " }\n" + + "\n" + + " return updatedAllCalcDataManager.getAllCalcDataManager(asgnmtId);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Obtains the updated AllCalcDataManager for the given assignment Id, if present. If not present, returns the\n" + + " * original ACDM instead. The assignment id must be given because in a\n" + + " * multiple assignment environment, a specific component needs to be requested, as originalAllCalcDataManager refers\n" + + " * to the aggregate assignment.\n" + + " *\n" + + " * @param asgnmtId The id of the assignment to load.\n" + + " * @return he updated AllCalcDataManager for the given assignment Id, if present. If not present, returns the\n" + + " * original ACDM instead.\n" + + " */\n" + + " AllCalcDataManager getLatestAllCalcDataManager(GeneratedId asgnmtId) {\n" + + " if (updatedAllCalcDataManager != null) {\n" + + " return getUpdatedAllCalcDataManager(asgnmtId);\n" + + " }\n" + + "\n" + + " return getOriginalAllCalcDataManager(asgnmtId);\n" + + " }\n" + + "\n" + + " //todo-ute: the method name is misleading as it may also create a new amended ACD if period is amendable by system on user's behalf\n" + + " private AllCalculationData getAllCalcData(AllCalcDataManager mgr, TimeSheetIdentifier id) {\n" + + " try {\n" + + " EmployeePeriodInfo epInfo = mgr.getEmployeePeriodInfo(id.getPpEnd());\n" + + " if (epInfo == null) {\n" + + " final PolicyID asgnmtPolicyProfile = mgr.getAsgnmtMaster().getPolicy_profile(id.getPpEnd());\n" + + " final PolicyID policyProfileId = (asgnmtPolicyProfile == null) ? PolicyID.EMPTY_POLICYID : asgnmtPolicyProfile;\n" + + " throw new InvalidPayPeriodException(\"EmployeePeriodInfo could not be obtained for employee \" +\n" + + " id.getEmployee() + \" and assignment \" + id.getAsgnmt() + \" and period end - \" + id.getPpEnd() + \". \" +\n" + + " \"Possibly because the employee is not active on that date or there are not enough initialized policy profile \" +\n" + + " \"periods for policy profile \" + policyProfileId );\n" + + " }\n" + + "\n" + + " TimeSheetCalculationInfo calcInfo = this.getCalculationInfo(mgr, id);\n" + + " AssignmentPeriodState asgnmtPeriodState = calcInfo.getAssignmentPeriodState();\n" + + " TimeSheetState timesheetState = calcInfo.getTimeSheetState();\n" + + " //If requesting EDIT_VERSION, check to see if one exists or that period is amendable by the logged in user\n" + + " if (id.getVersion() == EmployeePeriodVersionInfo.EDIT_VERSION && !epInfo.hasEditVersion()\n" + + " && !isPeriodAmendable(epInfo, mgr, asgnmtPeriodState, timesheetState) ) {\n" + + " throw new InternalApplicationException(\"An editable timesheet could not be obtained for employee \" +\n" + + " id.getEmployee() + \" and period end - \" + id.getPpEnd());\n" + + " }\n" + + "\n" + + " //Check to see if an editable ACD is in ACD manager's cache - if one exists, use it\n" + + " //An empty unmodifiable ACD for a prior period might be in cache if an ACD for prior timesheet is requested and\n" + + " //if that's the case, we want to still be able to create an amended ACD\n" + + " final AllCalculationData acd = mgr.getAllCalcData(id.getPpEnd(),id.getVersion(),AllCalculationData.FETCH_CACHED_ONLY);\n" + + " if (acd != null && !acd.isUnmodifiable()) {\n" + + " return acd;\n" + + " }\n" + + "\n" + + " //Create an amended ACD only if the period is amendable and the logged in user can amend it\n" + + " if (isAmendableBySystemForUser(epInfo, mgr, asgnmtPeriodState)) {\n" + + " final AllCalculationData amendedAllCalcData = mgr.createAmendedAllCalcData(epInfo, parms.getApp_user(), WDateTime.now());\n" + + " // No need to hook this up to approval change logic, since it is an in-memory change so far.\n" + + " //Recalculate the amended ACD so that timesheet exceptions (if any) get created on the amended timesheet\n" + + " amendedAllCalcData.recalc(mgr);\n" + + " }\n" + + "\n" + + " //By this time, the ACD mgr's ACD cache will have the amended ACD if one's created in this method\n" + + " //and we can retrieve the cached amended ACD or the ACD from the database\n" + + " return mgr.getAllCalcData(id.getPpEnd(), id.getVersion());\n" + + " } catch(InvalidPayPeriodException e) {\n" + + " //Rethrow InvalidPeriodExceptions--don't wrap them. We want them to be typed as InvalidPeriodExceptions.\n" + + " throw e;\n" + + " } catch (Exception e) {\n" + + " throw new InternalApplicationException(\"Timesheet could not be obtained for employee \" +\n" + + " id.getEmployee() + \", assignment \" + id.getAsgnmt()+ \", period end \" + id.getPpEnd() + \" and version \" + id.getVersion() + \".\", e);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * A period is amendable if one of the following is true:\n" + + " *
\n" +
+                   "   * - {@link #isAmendableBySystemForUser(EmployeePeriodInfo, AllCalcDataManager, AssignmentPeriodState)\n" +
+                   "   *    amendable by system on users's behalf}\n" +
+                   "   * - {@link TimeSheetState#isAmendable() amendable directly by user} generally by clicking \"Amend\" button on\n" +
+                   "   *   prior active period with one or more closed timesheets\n" +
+                   "   * 
\n" + + " */\n" + + " private static boolean isPeriodAmendable(EmployeePeriodInfo epInfo, AllCalcDataManager mgr,\n" + + " AssignmentPeriodState asgnmtPeriodState, TimeSheetState timesheetState)\n" + + " throws SQLException, MultipleRowDbRecException {\n" + + " return timesheetState.isAmendable() || isAmendableBySystemForUser(epInfo, mgr, asgnmtPeriodState);\n" + + " }\n" + + "\n" + + " /**\n" + + " * A period is amendable by system on user's behalf (not the same as AUTO_AMEND or SYS_AMEND which are also created by\n" + + " * system) if user has amend rights for the specified period as of today and if specified period is a priod period\n" + + " * with no timesheet or it's a prior period timesheet with just system amended but not user amended\n" + + " * @param epInfo\n" + + " * @param mgr\n" + + " * @param asgnmtPeriodState\n" + + " * @return\n" + + " * @throws PolicyLookupException\n" + + " * @throws MultipleRowDbRecException\n" + + " * @throws SQLException\n" + + " * @see AssignmentPeriodState#isPeriodAmendable() \n" + + " * @see Approval_event_type#SYS_AMEND\n" + + " * @see Approval_event_type#AMEND\n" + + " */\n" + + " private static boolean isAmendableBySystemForUser(EmployeePeriodInfo epInfo, AllCalcDataManager mgr,\n" + + " AssignmentPeriodState asgnmtPeriodState)\n" + + " throws MultipleRowDbRecException, SQLException {\n" + + " return asgnmtPeriodState.isPeriodAmendable() && // TODO: this doesn't match the javadoc?\n" + + " ( mgr.isPriorModifiablePeriodWithNoTimesheet(epInfo) || epInfo.isPriorPeriodWithSysAmendVersionOnly() );\n" + + " }\n" + + "\n" + + " private AllCalculationData getOriginalAllCalcData(TimeSheetIdentifier timeSheetIdentifier) {\n" + + " return getAllCalcData(getOriginalAllCalcDataManager(timeSheetIdentifier.getAsgnmt()), timeSheetIdentifier);\n" + + " }\n" + + "\n" + + " private AllCalculationData getUpdatedAllCalcData(TimeSheetIdentifier timeSheetIdentifier) {\n" + + " return getAllCalcData(getUpdatedAllCalcDataManager(timeSheetIdentifier.getAsgnmt()), timeSheetIdentifier);\n" + + " }\n" + + "\n" + + "\n" + + " private Set getTimeSheetIdentifiers() {\n" + + " return timeSheetIdentifiers;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns the pay period end for the period containing date provided, relative\n" + + " * to the defined periods for this assignment.\n" + + " *\n" + + " * @param aDateInUnknownPeriod any date we want to know the period for\n" + + " * @return never null\n" + + " */\n" + + " WDate getPpEndForDate(GeneratedId asgnmtId, WDate aDateInUnknownPeriod) {\n" + + " return TimeSchedUtils.getPpEndForDate(getOriginalAllCalcDataManager(asgnmtId), aDateInUnknownPeriod);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Assign system_record_id's to any DbRec objecs in the collection which lack them.\n" + + " * @param dbRecList List of DbRec objects on which to check the system_record_id. Records in list will be modified.\n" + + " */\n" + + " static private void assignSystemRecordIds(ListWrapBase dbRecList) {\n" + + " for (Iterator iterator = dbRecList.getCollection().iterator(); iterator.hasNext();) {\n" + + " DbRec dbRec = (DbRec) iterator.next();\n" + + " assignSystemRecordId(dbRec);\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Assign system_record_id to a DbRec object, if it is not set or contains a temporary id.\n" + + " * @param dbRec DbRec objects on which to check/change the system_record_id.\n" + + " */\n" + + " static private void assignSystemRecordId(DbRec dbRec) {\n" + + " if (dbRec.getSystem_record_id() == null || dbRec.getSystem_record_id().requiresPermanentId()) {\n" + + " dbRec.setSystem_record_id(SystemId.getNewID());\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Removes the last approval event (employee approval) from approval event list from the AllCalculationData object of\n" + + " * {@link #updatedAllCalcDataManager}\n" + + " * @param timeSheetId - Used to get the {@link AllCalculationData} object from {@link #updatedAllCalcDataManager}\n" + + " */\n" + + " void undoEmployeeApproval(TimeSheetIdentifier timeSheetId) {\n" + + " final AllCalculationData updatedAllCalcData = getUpdatedAllCalcData(timeSheetId);\n" + + " updatedAllCalcData.getApproval_eventList().removeLastApprovalEvent();\n" + + " }\n" + + "\n" + + " /**\n" + + " * Update the current object to be able to be used for other timesheets for this assignment\n" + + " * @param parms\n" + + " * @param timeSheetIdentifiers\n" + + " */\n" + + " public void update(TimeEntryParmsPerAssignment parms, Set timeSheetIdentifiers) {\n" + + " setClassFields(parms, timeSheetIdentifiers);\n" + + " }\n" + + "\n" + + " private void setClassFields(TimeEntryParmsPerAssignment parms, Set timeSheetIdentifiers) {\n" + + " this.parms = parms;\n" + + " this.timeSheetIdentifiers = TimeEntryParmsPerAssignment.TIMESHEET_IDENTIFIER_TREESET_FACTORY.newInstance();\n" + + " this.timeSheetIdentifiers.addAll(timeSheetIdentifiers);\n" + + " }\n" + + "\n" + + " /**\n" + + " * Recalculates and Saves timesheets (if saveData == true) for timesheet ids in {@link #parms} that belong to this\n" + + " * assignment\n" + + " *\n" + + " * @param trans\n" + + " * @return AggregateTimeEntryTransactionResults for all saved timesheets\n" + + " * @throws Exception in case of fatal error during timesheet recalc & save e.g. errors while store data in DB etc.\n" + + " * This precludes ConcurrentUserModification exception. Any exception thrown from this method, eventually gets\n" + + " * escalated as transaction exception\n" + + " */\n" + + " public TimeEntryTransactionResults recalcAndSaveTimesheets(TimeEntryTransaction trans, boolean saveData) throws Exception {\n" + + " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" + + " AllCalcDataManager tempAcdMgr = null;\n" + + " AggregateTimeEntryTransactionResults results = (AggregateTimeEntryTransactionResults) parms.getTimeEntryResultsFactory().newInstance();\n" + + " Set timeSheetIds = getAffectedTimeSheetIdentifiers(trans, parms);\n" + + " int retryCount = 0;\n" + + " boolean canRetryConcurrentModification = true;\n" + + " if (timeSheetIds.isEmpty()) {\n" + + " return results;\n" + + " }\n" + + "\n" + + " assert timeSheetIdentifiers.containsAll(timeSheetIds) : \"Attempted to recalculate a timesheet identifier not managed by this AssignmentManager.\";\n" + + " createUpdatedAllCalcDataManager(timeSheetIds);\n" + + "\n" + + " TransactionStatus timeSheetSaveStatus = null;\n" + + " Map applyResultsMap = new HashMap();\n" + + " for (TimeSheetIdentifier timeSheetId : timeSheetIds) {\n" + + " TimeSheetTransactionApplier applyResults = applyTransaction(timeSheetId, trans);\n" + + " if(applyResults.getApprovalEventType() != Approval_event_type.SAVE_SCHEDULE) {\n" + + " applyResultsMap.put(timeSheetId, applyResults);\n" + + " }\n" + + "\n" + + " recalc(timeSheetId, applyResults.getApprovalEventType());\n" + + "\n" + + " boolean isWithdrawal =\n" + + " (trans.findTransactionApprovalEvent(timeSheetId, Approval_event_type.WITHDRAWAL) != null);\n" + + " if (!isWithdrawal) {\n" + + " // There are 3 conditions identified in the following if statement that can prevent an \"action\" on the\n" + + " // current timesheet. The first is an exception that prevents save. In this case, we should never allow a\n" + + " // save of the timesheet to occur. The second condition is an exception that prevents submit. In this\n" + + " // condition, we should only allow a save if the user is not attempting to submit (apply an APPROVAL event).\n" + + " // The third case is errors that occur while applying transactions to the timesheet. An example of when this\n" + + " // may occur is if the same time sheet detail row is modified by 2 users. In all of these cases we prevent\n" + + " // the save operation from executing.\n" + + " // In the next condition where we check if an error has occurred, we remove the last approval event from the\n" + + " // timesheet if we did not save AND it was a submit (APPROVAL) event. Transactions only allow 1 event to be applied\n" + + " // to the timesheet. The transaction explicitly excludes SAVE_TIME_SHEET and SAVE_SCHEDULE events from being applied.\n" + + " // It relies on these events being applied only by the save operation itself. As a result, a maximum of 2 events may\n" + + " // be added to the timesheet as a result of the transaction (the event on the transaction if it was not a save event)\n" + + " // and the SAVE_TIME_SHEET or SAVE_SCHEDULE event added during the save operation.\n" + + "\n" + + " if (hasSubmitEvent(trans, timeSheetId) &&\n" + + " hasExceptionsPreventingSubmit(timeSheetId)) {\n" + + " timeSheetSaveStatus = getHighestPriorityError(timeSheetSaveStatus, Messages.EXCEPTIONS_PREVENT_SUBMIT);\n" + + " }\n" + + "\n" + + " if (hasExceptionsPreventingSave(timeSheetId)) {\n" + + " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" + + " Messages.EXCEPTIONS_PREVENT_SAVE.getLabel());\n" + + " }\n" + + " }\n" + + "\n" + + " if (applyResults.hasErrors()) {\n" + + " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" + + " Messages.TIME_SHEET_EXCEPTION_JAVA_EXCEPTION .getLabel());\n" + + " }\n" + + " }\n" + + "\n" + + "\n" + + " boolean errorPreventsAction = (timeSheetSaveStatus != null);\n" + + "\n" + + "\n" + + " Approval_event lastOriginalApprovalEvent = null;\n" + + " for (TimeSheetIdentifier timeSheetId : timeSheetIds) {\n" + + " TimeSheetTransactionApplier applyResults = applyResultsMap.get(timeSheetId);\n" + + " try {\n" + + " while (saveData && canRetryConcurrentModification) {\n" + + " if (errorPreventsAction) {\n" + + " // If the changes were not saved due to errors, and this operation (transaction) was an employee timesheet submission,\n" + + " // remove the approval event from the unsaved ACD.\n" + + " // NOTE: We do not have to concern ourselves with removing SAVE_TIME_SHEET or SAVE_SCHEDULE events here as these events\n" + + " // are explicitly EXCLUDED in the TimeSheetTransactionApplier (not applied) and are applied by the save operation (which\n" + + " // has not executed if we made it here.\n" + + " if (hasSubmitEvent(trans, timeSheetId)) {\n" + + " undoEmployeeApproval(timeSheetId);\n" + + " }\n" + + " } else {\n" + + " save(timeSheetId, applyResults.getApprovalEventType());\n" + + " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.SAVED,\n" + + " Messages.TIME_SHEET_SAVED.getLabel());\n" + + " postSave();\n" + + " }\n" + + " // All operations completed, so retries are no longer necessary\n" + + " canRetryConcurrentModification = false;\n" + + " }\n" + + " } catch (ConcurrentUserModificationException ex) {\n" + + " // Store the old AcdMgr, when a concurrent mod error due to another user modifying a time sheet occurs we want\n" + + " // to receive the same new approval events to ensure that the user is forced to reload the time sheet to continue.\n" + + " // TODO-13285: Remove the tempAcdMgr and make merging concurrently modified time sheets possible,\n" + + " // TODO-13285: requires considering multiple edge cases for the user interface\n" + + " if (tempAcdMgr == null) {\n" + + " tempAcdMgr = getOriginalAllCalcDataManager(asgnmtMaster.getAsgnmt());\n" + + " }\n" + + "\n" + + " if (retryCount == MAX_RETRIES) {\n" + + " // return the acdMgr to its previous state, this ensures the user must reload the time sheet\n" + + " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" + + " originalAllCalcDataManager = tempAcdMgr;\n" + + " GeneratedId errorId = ServerErrorLogger.singleton.log(new ServerError(\"Exception saving time sheet\", ex,\n" + + " Program_source.SERVER_REQUEST, parms.getApp_user().getLogin_id()));\n" + + " cat.error(\"Unable to save time sheet. debug_error_log id:\" + errorId, ex);\n" + + " //Use timeSheetSaveStatus to send concurrent error message to user.\n" + + " errorPreventsAction = true;\n" + + " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" + + " Messages.TIME_SHEET_EXCEPTION_CONCURRENT_USER.getLabel());\n" + + " // We have retried saving and have hit the MAX_RETRIES threshold, so stop retrying.\n" + + " canRetryConcurrentModification = false;\n" + + " } else {\n" + + " // On the first retry, we need to capture the last (most recent) approval event that existed on this timesheet prior to applying\n" + + " // any transactions because when we reload the \"original\" ACD to attempt to reapply the transactions we will lose track\n" + + " // of this (we'll pull in approval events for transactions that were saved in other sessions/processes). This is necessary to\n" + + " // allow more than 1 retry\n" + + " if (lastOriginalApprovalEvent == null) {\n" + + " lastOriginalApprovalEvent = getAllCalcData(getOriginalAllCalcDataManager(timeSheetId.getAsgnmt()), timeSheetId).getApproval_eventList().getLastApprovalEvent();\n" + + " }\n" + + " retryCount++;\n" + + " // attempt to reapply the transaction\n" + + " try {\n" + + " applyResults = attemptTimeSheetTransactionReapply(trans, timeSheetId, lastOriginalApprovalEvent);\n" + + " } catch (ConcurrentUserModificationException cume) {\n" + + " // return the acdMgr to its previous state, this ensures the user must reload the time sheet\n" + + " // TODO-13285: Remove this code and implement merging concurrently modified time sheets\n" + + " originalAllCalcDataManager = tempAcdMgr;\n" + + " // if we were unable to reapply transactions, no further retries are necessary (because it will just keep failing)\n" + + " // subsequent retries only succeed if we were able to reapply the new transactions successfully and still received\n" + + " // another ConcurrentUserModificationException (a second change occurred while applying transactions)\n" + + " canRetryConcurrentModification = false;\n" + + " errorPreventsAction = true;\n" + + " timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" + + " Messages.TIME_SHEET_EXCEPTION_CONCURRENT_USER.getLabel());\n" + + " }\n" + + " }\n" + + " }\n" + + " results.add(getDiff(timeSheetId, trans, applyResults, timeSheetSaveStatus, !errorPreventsAction));\n" + + " }\n" + + " return results;\n" + + " }\n" + + "\n" + + " "; + + String part2 = "\n" + + " private TransactionStatus getHighestPriorityError(TransactionStatus existingStatus, Message newError) {\n" + + " if(existingStatus == null) {\n" + + " return newError;\n" + + " }\n" + + " TransactionStatus timeSheetSaveStatus = new TransactionStatusImpl(Transaction_status.ERROR,\n" + + " );\n" + + " return timeSheetSaveStatus;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns true if the given TimeEntryTransaction has a submit event for the given time sheet.\n" + + " * @param trans TimeEntryTransaction to search\n" + + " * @param timeSheetId time sheet to search\n" + + " * @return true if a submit event exists\n" + + " */\n" + + " private static boolean hasSubmitEvent(TimeEntryTransaction trans, TimeSheetIdentifier timeSheetId) {\n" + + " return trans.findTransactionApprovalEvent(timeSheetId, Approval_event_type.APPROVAL) != null;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Resolves modifications that impact only non-\"input\" values on the timesheet to prevent them\n" + + " * from causing ConcurrentUserModificationExceptions on subsequent attempts at saving the data\n" + + " * @param trans time entry transaction to resolve\n" + + " * @param id time sheet ID\n" + + " * @param lastApprovalEvent the most recent approval event on the timesheet prior to any transactions\n" + + " * @return TimeSheetTransactionApplier if transactions were reapplied\n" + + " * @throws Exception on error loading the acd\n" + + " */\n" + + " private TimeSheetTransactionApplier attemptTimeSheetTransactionReapply(final TimeEntryTransaction trans, final TimeSheetIdentifier id, Approval_event lastApprovalEvent) throws Exception {\n" + + " // Clean out the ACDM cache to force reloads of the data from the database\n" + + " invalidateAllCalcDataManagers();\n" + + " AllCalculationData originalAcd = getAllCalcData(getOriginalAllCalcDataManager(id.getAsgnmt()), id);\n" + + " // Get any new approval events that exist in the database\n" + + " Approval_eventList newApprovalEvents = originalAcd.getApproval_eventList().getApprovalsSince(lastApprovalEvent);\n" + + " // Check if a \"concurrent save\" is possible given the events that have occurred\n" + + " if (newApprovalEvents.canConcurrentSave()) {\n" + + " //TODO:FIX\n" + + " //createUpdatedAllCalcDataManager(listOfAllIdsHere);\n" + + " TimeSheetTransactionApplier applyResults = applyTransaction(id, trans); // reapply transactions\n" + + " recalc(id, applyResults.getApprovalEventType()); // recalculate\n" + + " DbRecTime_sheet originalTimeSheet = originalAcd.getTime_sheet();\n" + + " DbRecTime_sheet updatedTimeSheet = getAllCalcData(getUpdatedAllCalcDataManager(id.getAsgnmt()), id).getTime_sheet();\n" + + " // Copy the system fields from the original time sheet to the updated time sheet because\n" + + " // we have determined at this point that the concurrent save is okay based on the difference\n" + + " // between these 2 time sheets. Copying these fields over will allow the save to complete\n" + + " // without causing a ConcurrentUserModificationException by making the update counter match.\n" + + " DbRecFieldCopier copier = new DbRecFieldCopier(DataDictionary.getCltnOfSystemFieldNames());\n" + + " copier.copyFields(originalTimeSheet, updatedTimeSheet);\n" + + " return applyResults;\n" + + " } else {\n" + + " // Otherwise, throw the exception\n" + + " throw new ConcurrentUserModificationException(\"Unable to save time sheet for assignment \" + originalAcd.getAsgnmtId() + \"due to concurrent changes.\");\n" + + " }\n" + + " }\n" + + "\n" + + " /**\n" + + " * Returns a set of AssignmentManagers that are affected by changes in the given transaction and managed by this\n" + + " * TimeEntryManager.\n" + + " *\n" + + " * @return a set of AssignmentManagers that are affected by changes in the given transaction and managed by this\n" + + " * TimeEntryManager.\n" + + " */\n" + + " public Set getAffectedTimeSheetIdentifiers(TimeEntryTransaction transaction, TimeEntryParmsPerAssignment parms) {\n" + + " Set affectedTimeSheetIds = new HashSet();\n" + + " for(GeneratedId assignmentId : getAssignmentIds()) {\n" + + " for(TimeSheetIdentifier timeSheetId : parms.getTimeSheetIdentifiersForAssignment(assignmentId)) {\n" + + " if(!transaction.obtainAllRows(timeSheetId).isEmpty()) {\n" + + " affectedTimeSheetIds.add(timeSheetId);\n" + + " }\n" + + " }\n" + + " }\n" + + " return affectedTimeSheetIds;\n" + + " }\n" + + "\n" + + " /**\n" + + " * Obtains all assignment id's that are managed by this AssignmentManager.\n" + + " *\n" + + " * @return all assignment id's that are managed by this AssignmentManager.\n" + + " */\n" + + " public Set getAssignmentIds() {\n" + + " Set assignmentIds = new HashSet();\n" + + " Asgnmt_masterList amList = asgnmtMaster.getCompAsgnmtMasters();\n" + + " for(Asgnmt_master am : amList) {\n" + + " assignmentIds.add(am.getAsgnmt());\n" + + " }\n" + + " return assignmentIds;\n" + + " }\n" + + "\n" + + " public GeneratedId getEmployee() {\n" + + " return asgnmtMaster.getEmployee();\n" + + " }\n" + + "\n" + + " /** Single or aggregate assignment master associated with this manager */\n" + + " private final Asgnmt_master asgnmtMaster;\n" + + "\n" + + " private TimeEntryParmsPerAssignment parms;\n" + + "\n" + + " /** Set of {@link TimeSheetIdentifier}'s which are managed by this object.\n" + + " * Composed of all the identifiers in the TimeEntryParms for this assignment and related components.\n" + + " */\n" + + " private Set timeSheetIdentifiers;\n" + + "\n" + + " /**\n" + + " * Original Single or Aggregate ACDM, as loaded from the database. ACD's in here are not modified.\n" + + " * This ACDM is lazily loaded, and should always be obtained from {@link #getOriginalAllCalcDataManager(GeneratedId)}.\n" + + " *\n" + + " * Null means that the ACDM has not yet been loaded from the database, and any attempts to use it should first\n" + + " * initialize it. Note: The meaning of null is different than updatedAllCalcDataManager's null meaning.\n" + + " */\n" + + " private AllCalcDataManager originalAllCalcDataManager = null;\n" + + "\n" + + " /**\n" + + " * Updated Single or Aggregate ACDM--a shallow copy of originalAllCalcDataManager. ACD's in here are modified by\n" + + " * TimeEntryTransactions. This ACDM is NOT lazily loaded--null means no changes present, and non-null means the\n" + + " * updated ACDM has been explicitly created.\n" + + " *\n" + + " * Null means that no changes have been made to the ACDM or data, and any attempts to use it should either throw a\n" + + " * descriptive error, or explicitly create the updatedAllCalcDataManager.\n" + + " * Note: The meaning of null is different than originalAllCalcDataManager's null meaning.\n" + + " */\n" + + " private AllCalcDataManager updatedAllCalcDataManager = null;\n" + + "\n" + + " private static final int MAX_RETRIES = 2;\n" + + "\n" + + " private static final Category cat=Category.getInstance(AssignmentManager.class.getName());\n" + + "}"; + configureFromFileText("Foo.java", part1 + part2); + + final PsiDocumentManager docManager = PsiDocumentManager.getInstance(ourProject); + final Document doc = docManager.getDocument(myFile); + doc.insertString(part1.length(), "/**"); + + boolean old = DebugUtil.CHECK; + DebugUtil.CHECK = true; + try { + docManager.commitAllDocuments(); + } + finally { + DebugUtil.CHECK = old; + } + } +}