content: /*******************************************************************************
   * Copyright (C) 2021, 1C-Soft LLC and others.
   *
   * This program and the accompanying materials are made
   * available under the terms of the Eclipse Public License 2.0
   * which is available at https://www.eclipse.org/legal/epl-2.0/
   *
   * SPDX-License-Identifier: EPL-2.0
   *
   * Contributors:
   *     1C-Soft LLC - initial API and implementation
   *******************************************************************************/
  package com.e1c.v8codestyle.autosort.itests;
  
  import static com._1c.g5.v8.dt.metadata.mdclass.MdClassPackage.Literals.CONFIGURATION;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertNotNull;
  import static org.junit.Assert.assertTrue;
  
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
  import java.util.UUID;
  import java.util.concurrent.CountDownLatch;
  import java.util.concurrent.TimeUnit;
  
  import org.eclipse.core.resources.IProject;
  import org.eclipse.core.runtime.IProgressMonitor;
  import org.eclipse.core.runtime.IStatus;
  import org.eclipse.core.runtime.NullProgressMonitor;
  import org.eclipse.core.runtime.preferences.IEclipsePreferences;
  import org.eclipse.emf.common.notify.Notification;
  import org.eclipse.emf.ecore.EReference;
  import org.eclipse.emf.ecore.EStructuralFeature;
  import org.junit.Ignore;
  import org.junit.Rule;
  import org.junit.Test;
  import org.junit.runner.RunWith;
  
  import com._1c.g5.v8.bm.core.IBmObject;
  import com._1c.g5.v8.bm.core.IBmTransaction;
  import com._1c.g5.v8.bm.core.event.BmChangeEvent;
  import com._1c.g5.v8.bm.core.event.BmEvent;
  import com._1c.g5.v8.bm.integration.AbstractBmTask;
  import com._1c.g5.v8.bm.integration.IBmModel;
  import com._1c.g5.v8.bm.integration.event.IBmSyncEventListener;
  import com._1c.g5.v8.dt.core.platform.IBmModelManager;
  import com._1c.g5.v8.dt.core.platform.IConfigurationAware;
  import com._1c.g5.v8.dt.core.platform.IDtProject;
  import com._1c.g5.v8.dt.core.platform.IDtProjectManager;
  import com._1c.g5.v8.dt.core.platform.IV8Project;
  import com._1c.g5.v8.dt.core.platform.IV8ProjectManager;
  import com._1c.g5.v8.dt.md.sort.MdSortPreferences;
  import com._1c.g5.v8.dt.metadata.mdclass.CommonModule;
  import com._1c.g5.v8.dt.metadata.mdclass.Configuration;
  import com._1c.g5.v8.dt.metadata.mdclass.MdClassFactory;
  import com._1c.g5.v8.dt.testing.GuiceModules;
  import com._1c.g5.v8.dt.testing.JUnitGuiceRunner;
  import com._1c.g5.v8.dt.testing.TestingWorkspace;
  import com.e1c.v8codestyle.autosort.AutoSortPreferences;
  import com.e1c.v8codestyle.autosort.ISortService;
  import com.google.common.base.Preconditions;
  import com.google.inject.Inject;
  
  /**
   * Tests for {@link ISortService}
   */
  @RunWith(JUnitGuiceRunner.class)
  @GuiceModules(modules = { ExternalDependenciesModule.class })
  public class SortServiceTest
  {
      private static final String PROJECT_NAME = "Sort";
  
      @Rule
      public TestingWorkspace testingWorkspace = new TestingWorkspace(true, true);
  
      @Inject
      private ISortService sortService;
  
      @Inject
      public IDtProjectManager dtProjectManager;
  
      @Inject
      private IV8ProjectManager v8ProjectManager;
  
      @Inject
      public IBmModelManager bmModelManager;
  
      @Test
      public void testSortOff() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
  
          IStatus status = sortService.sortAllMetadata(dtProject, new NullProgressMonitor());
          assertTrue(status.isOK());
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          assertTrue(object instanceof Configuration);
  
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("ГМодуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(4).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(5).getName());
      }
  
      @Test
      public void testSortSortTop() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
  
          IEclipsePreferences autoSortPrefs = AutoSortPreferences.getPreferences(project);
          autoSortPrefs.putBoolean(AutoSortPreferences.KEY_ALL_TOP, true);
          autoSortPrefs.flush();
  
          IEclipsePreferences mdSortPrefs = MdSortPreferences.getPreferences(project);
          mdSortPrefs.putBoolean(MdSortPreferences.NATURAL_SORT_ORDER, true);
          mdSortPrefs.flush();
  
          IStatus status = sortService.sortAllMetadata(dtProject, new NullProgressMonitor());
          assertTrue(status.isOK());
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          assertTrue(object instanceof Configuration);
  
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ГМодуль", configuration.getCommonModules().get(4).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(5).getName());
      }
  
      @Test
      public void testSortOrderAsDesigner() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
  
          IEclipsePreferences autoSortPrefs = AutoSortPreferences.getPreferences(project);
          autoSortPrefs.putBoolean(AutoSortPreferences.KEY_ALL_TOP, true);
          autoSortPrefs.flush();
  
          IEclipsePreferences mdSortPrefs = MdSortPreferences.getPreferences(project);
          mdSortPrefs.putBoolean(MdSortPreferences.NATURAL_SORT_ORDER, false);
          mdSortPrefs.flush();
  
          IStatus status = sortService.sortAllMetadata(dtProject, new NullProgressMonitor());
          assertTrue(status.isOK());
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          assertTrue(object instanceof Configuration);
  
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ГМодуль", configuration.getCommonModules().get(4).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(5).getName());
      }
  
      @Ignore("#1365")
      @Test
      public void testSortAfterRemoveEvent() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
          IV8Project v8Project = v8ProjectManager.getProject(dtProject);
          assertNotNull(dtProject);
  
          IEclipsePreferences autoSortPrefs = AutoSortPreferences.getPreferences(project);
          autoSortPrefs.putBoolean(AutoSortPreferences.KEY_ALL_TOP, true);
          autoSortPrefs.flush();
  
          IEclipsePreferences mdSortPrefs = MdSortPreferences.getPreferences(project);
          mdSortPrefs.putBoolean(MdSortPreferences.NATURAL_SORT_ORDER, false);
          mdSortPrefs.flush();
  
          CountDownLatch waitSortStartedlatch = new CountDownLatch(1);
          bmModelManager.addSyncEventListener(bmModelManager.getBmNamespace(project),
              new BmEventListener(project, waitSortStartedlatch)
              {
                  boolean isBmObjectRemoved = false;
                  boolean isStortStarted = false;
  
                  @Override
                  protected void registerEvent(Notification notification)
                  {
                      if (notification.getEventType() == Notification.REMOVE)
                      {
                          isBmObjectRemoved = true;
                      }
                      else if (notification.getEventType() == Notification.MOVE)
                      {
                          isStortStarted = true;
                      }
                  }
  
                  @Override
                  protected boolean isShouldCountDownLatch()
                  {
                      return isBmObjectRemoved && isStortStarted;
                  }
              });
  
          bmModelManager.getModel(project).execute(new AbstractBmTask<Void>("Detach a top object") //$NON-NLS-1$
          {
              @Override
              public Void execute(IBmTransaction transaction, IProgressMonitor monitor)
              {
                  IBmObject commonModule = transaction.getTopObjectByFqn("CommonModule.ГМодуль");
                  Configuration configuration =
                      transaction.toTransactionObject(((IConfigurationAware)v8Project).getConfiguration());
                  configuration.getCommonModules().remove((CommonModule)commonModule);
                  transaction.detachTopObject(commonModule);
                  return null;
              }
          });
  
          waitSortStartedlatch.await();
          // wait until sorting is completed.
          TimeUnit.SECONDS.sleep(5);
  
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          assertTrue(object instanceof Configuration);
  
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(4).getName());
      }
  
      @Ignore("#1365")
      @Test
      public void testSortAfterMoveEvent() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
          IV8Project v8Project = v8ProjectManager.getProject(dtProject);
          assertNotNull(dtProject);
  
          IEclipsePreferences autoSortPrefs = AutoSortPreferences.getPreferences(project);
          autoSortPrefs.putBoolean(AutoSortPreferences.KEY_ALL_TOP, true);
          autoSortPrefs.flush();
  
          IEclipsePreferences mdSortPrefs = MdSortPreferences.getPreferences(project);
          mdSortPrefs.putBoolean(MdSortPreferences.NATURAL_SORT_ORDER, false);
          mdSortPrefs.flush();
  
          CountDownLatch waitSortStartedlatch = new CountDownLatch(1);
          bmModelManager.addSyncEventListener(bmModelManager.getBmNamespace(project),
              new BmEventListener(project, waitSortStartedlatch)
              {
                  private int moveCount = 0;
  
                  @Override
                  protected void registerEvent(Notification notification)
                  {
                      if (notification.getEventType() == Notification.MOVE)
                      {
                          moveCount++;
                      }
                  }
  
                  @Override
                  protected boolean isShouldCountDownLatch()
                  {
                      return moveCount == 2;
                  }
              });
  
          bmModelManager.getModel(project).execute(new AbstractBmTask<Void>("Move a top object") //$NON-NLS-1$
          {
              @Override
              public Void execute(IBmTransaction transaction, IProgressMonitor progressMonitor)
              {
                  IBmObject commonModule = transaction.getTopObjectByFqn("CommonModule.БМодуль");
                  Configuration configuration =
                      transaction.toTransactionObject(((IConfigurationAware)v8Project).getConfiguration());
                  configuration.getCommonModules().move(4, (CommonModule)commonModule);
                  return null;
              }
          });
  
          waitSortStartedlatch.await();
          // wait until sorting is performed.
          TimeUnit.SECONDS.sleep(5);
  
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          assertTrue(object instanceof Configuration);
  
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ГМодуль", configuration.getCommonModules().get(4).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(5).getName());
      }
  
      @Ignore("#1365")
      @Test
      public void testSortAfterAddEvent() throws Exception
      {
          IProject project = testingWorkspace.setUpProject(PROJECT_NAME, getClass());
          assertNotNull(project);
          IDtProject dtProject = dtProjectManager.getDtProject(project);
          assertNotNull(dtProject);
          IV8Project v8Project = v8ProjectManager.getProject(dtProject);
          assertNotNull(dtProject);
  
          IEclipsePreferences autoSortPrefs = AutoSortPreferences.getPreferences(project);
          autoSortPrefs.putBoolean(AutoSortPreferences.KEY_ALL_TOP, true);
          autoSortPrefs.flush();
  
          IEclipsePreferences mdSortPrefs = MdSortPreferences.getPreferences(project);
          mdSortPrefs.putBoolean(MdSortPreferences.NATURAL_SORT_ORDER, false);
          mdSortPrefs.flush();
  
          CountDownLatch waitSortStartedlatch = new CountDownLatch(1);
          bmModelManager.addSyncEventListener(bmModelManager.getBmNamespace(project),
              new BmEventListener(project, waitSortStartedlatch)
              {
                  boolean isBmObjectAdded = false;
                  boolean isStortStarted = false;
  
                  @Override
                  protected void registerEvent(Notification notification)
                  {
                      if (notification.getEventType() == Notification.ADD)
                      {
                          isBmObjectAdded = true;
                      }
                      else if (notification.getEventType() == Notification.MOVE)
                      {
                          isStortStarted = true;
                      }
                  }
  
                  @Override
                  protected boolean isShouldCountDownLatch()
                  {
                      return isBmObjectAdded && isStortStarted;
                  }
              });
  
          bmModelManager.getModel(project).execute(new AbstractBmTask<Void>("Add a top object") //$NON-NLS-1$
          {
              @Override
              public Void execute(IBmTransaction transaction, IProgressMonitor progressMonitor)
              {
                  CommonModule commonModule = MdClassFactory.eINSTANCE.createCommonModule();
                  commonModule.setName("КМодуль");
                  commonModule.setUuid(UUID.randomUUID());
                  transaction.attachTopObject((IBmObject)commonModule, "CommonModule.КМодуль");
  
                  Configuration configuration =
                      transaction.toTransactionObject(((IConfigurationAware)v8Project).getConfiguration());
  
                  configuration.getCommonModules().add(commonModule);
                  return null;
              }
          });
  
          waitSortStartedlatch.await();
          // wait until sorting is performed.
          TimeUnit.SECONDS.sleep(5);
  
          IBmObject object = getTopObjectByFqn(CONFIGURATION.getName(), dtProject);
          Configuration configuration = (Configuration)object;
          assertFalse(configuration.getCommonModules().isEmpty());
  
          assertEquals("АМ_Модуль", configuration.getCommonModules().get(0).getName());
          assertEquals("АМ2_4Модуль", configuration.getCommonModules().get(1).getName());
          assertEquals("АМодуль", configuration.getCommonModules().get(2).getName());
          assertEquals("БМодуль", configuration.getCommonModules().get(3).getName());
          assertEquals("ГМодуль", configuration.getCommonModules().get(4).getName());
          assertEquals("КМодуль", configuration.getCommonModules().get(5).getName());
          assertEquals("ОбщийМодуль", configuration.getCommonModules().get(6).getName());
      }
  
      protected IBmObject getTopObjectByFqn(final String fqn, IDtProject dtProject)
      {
          IBmModel model = this.bmModelManager.getModel(dtProject);
          return model.executeReadonlyTask(new AbstractBmTask<IBmObject>("GetObject")
          {
              @Override
              public IBmObject execute(IBmTransaction transaction, IProgressMonitor progressMonitor)
              {
                  return transaction.getTopObjectByFqn(fqn);
              }
          });
      }
  
      private abstract class BmEventListener
          implements IBmSyncEventListener
      {
          private final CountDownLatch latch;
          private final IProject project;
  
          /**
           * Creates instance of listener.
           *
           * @param project project to listen events to, cannot be {@code null}
           * @param latch latch waiting until notified about {@code eventType} and starting sort, cannot be {@code null}
           */
          public BmEventListener(IProject project, CountDownLatch latch)
          {
              this.project = Preconditions.checkNotNull(project);
              this.latch = Preconditions.checkNotNull(latch);
          }
  
          @Override
          public void handleSyncEvent(BmEvent event)
          {
              for (BmChangeEvent changeEvent : event.getChangeEvents().values())
              {
                  if (changeEvent.getObject() instanceof IBmObject && ((IBmObject)changeEvent.getObject()).bmIsTop()
                      && ((IBmObject)changeEvent.getObject()).bmGetFqn() != null)
                  {
                      Map<EStructuralFeature, List<Notification>> notifications = changeEvent.getNotifications();
                      for (Entry<EStructuralFeature, List<Notification>> entry : notifications.entrySet())
                      {
                          if (entry.getKey() instanceof EReference
                              && AutoSortPreferences.isAllowedToSort(project, (EReference)entry.getKey()))
                          {
                              for (Notification notification : entry.getValue())
                              {
                                  registerEvent(notification);
                              }
                          }
                      }
                  }
              }
              if (isShouldCountDownLatch())
              {
                  latch.countDown();
              }
          }
  
          /**
           * Specifies how event must be registered.
           *
           * @param notification the notification containing event data, cannot be {@code null}
           */
          protected abstract void registerEvent(Notification notification);
  
          /**
           * Checks if listener should count down latch.
           *
           * @return {@code true} if should count down latch, {@code false} otherwise
           */
          protected abstract boolean isShouldCountDownLatch();
      }
  }
  