Example #1
0
 private void keys(Node x, Queue<Key> queue, Key lo, Key hi) {
   if (x == null) return;
   int compLo = lo.compareTo(x.key);
   int compHi = hi.compareTo(x.key);
   if (compLo < 0) keys(x.left, queue, lo, hi);
   if (compLo <= 0 && compHi >= 0) queue.enqueue(x.key);
   if (compHi > 0) keys(x.right, queue, lo, hi);
 }
Example #2
0
 private Node delete(Node x, Key key) {
   if (x == null) return null;
   int comp = key.compareTo(x.key);
   if (comp < 0) x.left = delete(x.left, key);
   else if (comp > 0) x.right = delete(x.right, key);
   else {
     if (x.left == null) {
       if (x.pred != null) x.pred.succ = x.succ;
       if (x.succ != null) x.succ.pred = x.pred;
       return x.right;
     }
     if (x.right == null) {
       if (x.pred != null) x.pred.succ = x.succ;
       if (x.succ != null) x.succ.pred = x.pred;
       return x.left;
     }
     Node t = max(x.left);
     t.left = deleteMaxWithOutDeleteThread(x.left);
     t.right = x.right;
     if (x.pred != null) x.pred.succ = x.succ;
     if (x.succ != null) x.succ.pred = x.pred;
     x = t;
   }
   x.N = size(x.left) + size(x.right) + 1;
   x.height = Math.max(height(x.left), height(x.right)) + 1;
   x.avgCompares =
       (avgCompares(x.left) * size(x.left) + avgCompares(x.right) * size(x.right) + size(x))
           / size(x);
   return x;
 }
Example #3
0
 private int rank(Node x, Key key) {
   if (x == null) return 0;
   int comp = key.compareTo(x.key);
   if (comp == 0) return size(x.left);
   else if (comp < 0) return rank(x.left, key);
   else return size(x.left) + 1 + rank(x.right, key);
 }
Example #4
0
 private Node put(Node x, Key key, Value val) {
   if (x == null) {
     Node pre = floor(root, key);
     Node nex = ceiling(root, key);
     lastest = new Node(key, val, 1, 1, 1);
     if (pre != null) pre.succ = lastest;
     if (nex != null) nex.pred = lastest;
     lastest.succ = nex;
     lastest.pred = pre;
     return lastest;
   }
   int comp = key.compareTo(x.key);
   if (comp < 0) x.left = put(x.left, key, val);
   else if (comp > 0) x.right = put(x.right, key, val);
   else {
     lastest = x;
     x.value = val;
   }
   x.N = size(x.right) + size(x.left) + 1;
   x.height = Math.max(height(x.left), height(x.right)) + 1;
   x.avgCompares =
       (avgCompares(x.left) * size(x.left) + avgCompares(x.right) * size(x.right) + size(x))
           / size(x);
   return x;
 }
Example #5
0
 /*   private Node get(Node x, Key key) //recursive implementation
 {
     if(x == null)
         return null;
     int com = key.compareTo(x.key);
     if(com < 0) return get(x.left, key);
     else if(com > 0) return get(x.right, key);
     else return x;
 }*/
 private Node get(Node x, Key key) {
   while (x != null) {
     int comp = key.compareTo(x.key);
     if (comp < 0) x = x.left;
     else if (comp > 0) x = x.right;
     else return x;
   }
   return null;
 }
Example #6
0
 private Node floor(Node x, Key key) {
   if (x == null) return null;
   int comp = key.compareTo(x.key);
   if (comp < 0) return floor(x.left, key);
   else if (comp > 0) {
     Node res = floor(x.right, key);
     if (res != null) return res;
     else return x;
   } else return x;
 }
Example #7
0
 private Node ceiling(Node x, Key key) {
   if (x == null) return null;
   int comp = key.compareTo(x.key);
   if (comp > 0) return ceiling(x.right, key);
   else if (comp == 0) return x;
   else {
     Node res = ceiling(x.left, key);
     if (res == null) return x;
     else return res;
   }
 }
/** @author peter */
@SuppressWarnings("UseOfSystemOutOrSystemErr")
public abstract class UsefulTestCase extends TestCase {
  public static final boolean IS_UNDER_TEAMCITY = System.getenv("TEAMCITY_VERSION") != null;

  public static final String IDEA_MARKER_CLASS =
      "com.intellij.openapi.components.impl.stores.IdeaProjectStoreImpl";
  public static final String TEMP_DIR_MARKER = "unitTest_";

  protected static boolean OVERWRITE_TESTDATA = false;

  private static final String DEFAULT_SETTINGS_EXTERNALIZED;
  private static final Random RNG = new SecureRandom();
  private static final String ORIGINAL_TEMP_DIR = FileUtil.getTempDirectory();

  protected final Disposable myTestRootDisposable =
      new Disposable() {
        @Override
        public void dispose() {}

        @Override
        public String toString() {
          String testName = getTestName(false);
          return UsefulTestCase.this.getClass()
              + (StringUtil.isEmpty(testName) ? "" : ".test" + testName);
        }
      };

  protected static String ourPathToKeep = null;

  private CodeStyleSettings myOldCodeStyleSettings;
  private String myTempDir;

  protected static final Key<String> CREATION_PLACE = Key.create("CREATION_PLACE");

  static {
    // Radar #5755208: Command line Java applications need a way to launch without a Dock icon.
    System.setProperty("apple.awt.UIElement", "true");

    try {
      CodeInsightSettings defaultSettings = new CodeInsightSettings();
      Element oldS = new Element("temp");
      defaultSettings.writeExternal(oldS);
      DEFAULT_SETTINGS_EXTERNALIZED = JDOMUtil.writeElement(oldS, "\n");
    } catch (Exception e) {
      throw new RuntimeException(e);
    }
  }

  protected boolean shouldContainTempFiles() {
    return true;
  }

  @Override
  protected void setUp() throws Exception {
    super.setUp();

    if (shouldContainTempFiles()) {
      String testName = getTestName(true);
      if (StringUtil.isEmptyOrSpaces(testName)) testName = "";
      testName = new File(testName).getName(); // in case the test name contains file separators
      myTempDir =
          FileUtil.toSystemDependentName(
              ORIGINAL_TEMP_DIR + "/" + TEMP_DIR_MARKER + testName + "_" + RNG.nextInt(1000));
      FileUtil.resetCanonicalTempPathCache(myTempDir);
    }
    //noinspection AssignmentToStaticFieldFromInstanceMethod
    DocumentImpl.CHECK_DOCUMENT_CONSISTENCY = !isPerformanceTest();
  }

  @Override
  protected void tearDown() throws Exception {
    try {
      Disposer.dispose(myTestRootDisposable);
      cleanupSwingDataStructures();
      cleanupDeleteOnExitHookList();
    } finally {
      if (shouldContainTempFiles()) {
        FileUtil.resetCanonicalTempPathCache(ORIGINAL_TEMP_DIR);
        if (ourPathToKeep != null && FileUtil.isAncestor(myTempDir, ourPathToKeep, false)) {
          File[] files = new File(myTempDir).listFiles();
          if (files != null) {
            for (File file : files) {
              if (!FileUtil.pathsEqual(file.getPath(), ourPathToKeep)) {
                FileUtil.delete(file);
              }
            }
          }
        } else {
          FileUtil.delete(new File(myTempDir));
        }
      }
    }

    UIUtil.removeLeakingAppleListeners();
    super.tearDown();
  }

  private static final Set<String> DELETE_ON_EXIT_HOOK_DOT_FILES;
  private static final Class DELETE_ON_EXIT_HOOK_CLASS;

  static {
    Class<?> aClass = null;
    Set<String> files = null;
    try {
      aClass = Class.forName("java.io.DeleteOnExitHook");
      files = ReflectionUtil.getField(aClass, null, Set.class, "files");
    } catch (Exception ignored) {
    }
    DELETE_ON_EXIT_HOOK_CLASS = aClass;
    DELETE_ON_EXIT_HOOK_DOT_FILES = files;
  }

  public static void cleanupDeleteOnExitHookList()
      throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
    // try to reduce file set retained by java.io.DeleteOnExitHook
    List<String> list;
    synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
      if (DELETE_ON_EXIT_HOOK_DOT_FILES.isEmpty()) return;
      list = new ArrayList<String>(DELETE_ON_EXIT_HOOK_DOT_FILES);
    }
    for (int i = list.size() - 1; i >= 0; i--) {
      String path = list.get(i);
      if (FileSystemUtil.getAttributes(path) == null || new File(path).delete()) {
        synchronized (DELETE_ON_EXIT_HOOK_CLASS) {
          DELETE_ON_EXIT_HOOK_DOT_FILES.remove(path);
        }
      }
    }
  }

  private static void cleanupSwingDataStructures() throws Exception {
    Class<?> aClass = Class.forName("javax.swing.KeyboardManager");

    Method get = aClass.getMethod("getCurrentManager");
    get.setAccessible(true);
    Object manager = get.invoke(null);
    {
      Field mapF = aClass.getDeclaredField("componentKeyStrokeMap");
      mapF.setAccessible(true);
      Object map = mapF.get(manager);
      ((Map) map).clear();
    }
    {
      Field mapF = aClass.getDeclaredField("containerMap");
      mapF.setAccessible(true);
      Object map = mapF.get(manager);
      ((Map) map).clear();
    }
  }

  protected CompositeException checkForSettingsDamage() throws Exception {
    Application app = ApplicationManager.getApplication();
    if (isPerformanceTest() || app == null || app instanceof MockApplication) {
      return new CompositeException();
    }

    CodeStyleSettings oldCodeStyleSettings = myOldCodeStyleSettings;
    myOldCodeStyleSettings = null;

    return doCheckForSettingsDamage(oldCodeStyleSettings, getCurrentCodeStyleSettings());
  }

  public static CompositeException doCheckForSettingsDamage(
      @NotNull CodeStyleSettings oldCodeStyleSettings,
      @NotNull CodeStyleSettings currentCodeStyleSettings)
      throws Exception {
    CompositeException result = new CompositeException();
    final CodeInsightSettings settings = CodeInsightSettings.getInstance();
    try {
      Element newS = new Element("temp");
      settings.writeExternal(newS);
      Assert.assertEquals(
          "Code insight settings damaged",
          DEFAULT_SETTINGS_EXTERNALIZED,
          JDOMUtil.writeElement(newS, "\n"));
    } catch (AssertionError error) {
      CodeInsightSettings clean = new CodeInsightSettings();
      Element temp = new Element("temp");
      clean.writeExternal(temp);
      settings.loadState(temp);
      result.add(error);
    }

    currentCodeStyleSettings.getIndentOptions(StdFileTypes.JAVA);
    try {
      checkSettingsEqual(
          oldCodeStyleSettings, currentCodeStyleSettings, "Code style settings damaged");
    } catch (AssertionError e) {
      result.add(e);
    } finally {
      currentCodeStyleSettings.clearCodeStyleSettings();
    }

    try {
      InplaceRefactoring.checkCleared();
    } catch (AssertionError e) {
      result.add(e);
    }
    try {
      StartMarkAction.checkCleared();
    } catch (AssertionError e) {
      result.add(e);
    }

    return result;
  }

  protected void storeSettings() {
    if (!isPerformanceTest() && ApplicationManager.getApplication() != null) {
      myOldCodeStyleSettings = getCurrentCodeStyleSettings().clone();
      myOldCodeStyleSettings.getIndentOptions(StdFileTypes.JAVA);
    }
  }

  protected CodeStyleSettings getCurrentCodeStyleSettings() {
    if (CodeStyleSchemes.getInstance().getCurrentScheme() == null) return new CodeStyleSettings();
    return CodeStyleSettingsManager.getInstance().getCurrentSettings();
  }

  public Disposable getTestRootDisposable() {
    return myTestRootDisposable;
  }

  @Override
  protected void runTest() throws Throwable {
    final Throwable[] throwables = new Throwable[1];

    Runnable runnable =
        new Runnable() {
          @Override
          public void run() {
            try {
              UsefulTestCase.super.runTest();
            } catch (InvocationTargetException e) {
              e.fillInStackTrace();
              throwables[0] = e.getTargetException();
            } catch (IllegalAccessException e) {
              e.fillInStackTrace();
              throwables[0] = e;
            } catch (Throwable e) {
              throwables[0] = e;
            }
          }
        };

    invokeTestRunnable(runnable);

    if (throwables[0] != null) {
      throw throwables[0];
    }
  }

  protected boolean shouldRunTest() {
    return PlatformTestUtil.canRunTest(getClass());
  }

  public static void edt(Runnable r) {
    UIUtil.invokeAndWaitIfNeeded(r);
  }

  protected void invokeTestRunnable(@NotNull Runnable runnable) throws Exception {
    UIUtil.invokeAndWaitIfNeeded(runnable);
    // runnable.run();
  }

  protected void defaultRunBare() throws Throwable {
    super.runBare();
  }

  @Override
  public void runBare() throws Throwable {
    if (!shouldRunTest()) return;

    if (runInDispatchThread()) {
      final Throwable[] exception = {null};
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              try {
                defaultRunBare();
              } catch (Throwable tearingDown) {
                if (exception[0] == null) exception[0] = tearingDown;
              }
            }
          });
      if (exception[0] != null) throw exception[0];
    } else {
      defaultRunBare();
    }
  }

  protected boolean runInDispatchThread() {
    return true;
  }

  @NonNls
  public static String toString(Iterable<?> collection) {
    if (!collection.iterator().hasNext()) {
      return "<empty>";
    }

    final StringBuilder builder = new StringBuilder();
    for (final Object o : collection) {
      if (o instanceof THashSet) {
        builder.append(new TreeSet<Object>((THashSet) o));
      } else {
        builder.append(o);
      }
      builder.append("\n");
    }
    return builder.toString();
  }

  public static <T> void assertOrderedEquals(T[] actual, T... expected) {
    assertOrderedEquals(Arrays.asList(actual), expected);
  }

  public static <T> void assertOrderedEquals(Iterable<T> actual, T... expected) {
    assertOrderedEquals(null, actual, expected);
  }

  public static void assertOrderedEquals(@NotNull byte[] actual, @NotNull byte[] expected) {
    assertEquals(actual.length, expected.length);
    for (int i = 0; i < actual.length; i++) {
      byte a = actual[i];
      byte e = expected[i];
      assertEquals("not equals at index: " + i, e, a);
    }
  }

  public static <T> void assertOrderedEquals(
      final String errorMsg, @NotNull Iterable<T> actual, @NotNull T... expected) {
    Assert.assertNotNull(actual);
    Assert.assertNotNull(expected);
    assertOrderedEquals(errorMsg, actual, Arrays.asList(expected));
  }

  public static <T> void assertOrderedEquals(
      final Iterable<? extends T> actual, final Collection<? extends T> expected) {
    assertOrderedEquals(null, actual, expected);
  }

  public static <T> void assertOrderedEquals(
      final String erroMsg,
      final Iterable<? extends T> actual,
      final Collection<? extends T> expected) {
    ArrayList<T> list = new ArrayList<T>();
    for (T t : actual) {
      list.add(t);
    }
    if (!list.equals(new ArrayList<T>(expected))) {
      String expectedString = toString(expected);
      String actualString = toString(actual);
      Assert.assertEquals(erroMsg, expectedString, actualString);
      Assert.fail(
          "Warning! 'toString' does not reflect the difference.\nExpected: "
              + expectedString
              + "\nActual: "
              + actualString);
    }
  }

  public static <T> void assertOrderedCollection(T[] collection, @NotNull Consumer<T>... checkers) {
    Assert.assertNotNull(collection);
    assertOrderedCollection(Arrays.asList(collection), checkers);
  }

  public static <T> void assertSameElements(T[] collection, T... expected) {
    assertSameElements(Arrays.asList(collection), expected);
  }

  public static <T> void assertSameElements(Collection<? extends T> collection, T... expected) {
    assertSameElements(collection, Arrays.asList(expected));
  }

  public static <T> void assertSameElements(
      Collection<? extends T> collection, Collection<T> expected) {
    assertSameElements(null, collection, expected);
  }

  public static <T> void assertSameElements(
      String message, Collection<? extends T> collection, Collection<T> expected) {
    assertNotNull(collection);
    assertNotNull(expected);
    if (collection.size() != expected.size()
        || !new HashSet<T>(expected).equals(new HashSet<T>(collection))) {
      Assert.assertEquals(message, toString(expected, "\n"), toString(collection, "\n"));
      Assert.assertEquals(message, new HashSet<T>(expected), new HashSet<T>(collection));
    }
  }

  public <T> void assertContainsOrdered(Collection<? extends T> collection, T... expected) {
    assertContainsOrdered(collection, Arrays.asList(expected));
  }

  public <T> void assertContainsOrdered(
      Collection<? extends T> collection, Collection<T> expected) {
    ArrayList<T> copy = new ArrayList<T>(collection);
    copy.retainAll(expected);
    assertOrderedEquals(toString(collection), copy, expected);
  }

  public <T> void assertContainsElements(Collection<? extends T> collection, T... expected) {
    assertContainsElements(collection, Arrays.asList(expected));
  }

  public <T> void assertContainsElements(
      Collection<? extends T> collection, Collection<T> expected) {
    ArrayList<T> copy = new ArrayList<T>(collection);
    copy.retainAll(expected);
    assertSameElements(toString(collection), copy, expected);
  }

  public static String toString(Object[] collection, String separator) {
    return toString(Arrays.asList(collection), separator);
  }

  public <T> void assertDoesntContain(Collection<? extends T> collection, T... notExpected) {
    assertDoesntContain(collection, Arrays.asList(notExpected));
  }

  public <T> void assertDoesntContain(
      Collection<? extends T> collection, Collection<T> notExpected) {
    ArrayList<T> expected = new ArrayList<T>(collection);
    expected.removeAll(notExpected);
    assertSameElements(collection, expected);
  }

  public static String toString(Collection<?> collection, String separator) {
    List<String> list =
        ContainerUtil.map2List(
            collection,
            new Function<Object, String>() {
              @Override
              public String fun(final Object o) {
                return String.valueOf(o);
              }
            });
    Collections.sort(list);
    StringBuilder builder = new StringBuilder();
    boolean flag = false;
    for (final String o : list) {
      if (flag) {
        builder.append(separator);
      }
      builder.append(o);
      flag = true;
    }
    return builder.toString();
  }

  public static <T> void assertOrderedCollection(
      Collection<? extends T> collection, Consumer<T>... checkers) {
    Assert.assertNotNull(collection);
    if (collection.size() != checkers.length) {
      Assert.fail(toString(collection));
    }
    int i = 0;
    for (final T actual : collection) {
      try {
        checkers[i].consume(actual);
      } catch (AssertionFailedError e) {
        System.out.println(i + ": " + actual);
        throw e;
      }
      i++;
    }
  }

  public static <T> void assertUnorderedCollection(T[] collection, Consumer<T>... checkers) {
    assertUnorderedCollection(Arrays.asList(collection), checkers);
  }

  public static <T> void assertUnorderedCollection(
      Collection<? extends T> collection, Consumer<T>... checkers) {
    Assert.assertNotNull(collection);
    if (collection.size() != checkers.length) {
      Assert.fail(toString(collection));
    }
    Set<Consumer<T>> checkerSet = new HashSet<Consumer<T>>(Arrays.asList(checkers));
    int i = 0;
    Throwable lastError = null;
    for (final T actual : collection) {
      boolean flag = true;
      for (final Consumer<T> condition : checkerSet) {
        Throwable error = accepts(condition, actual);
        if (error == null) {
          checkerSet.remove(condition);
          flag = false;
          break;
        } else {
          lastError = error;
        }
      }
      if (flag) {
        lastError.printStackTrace();
        Assert.fail("Incorrect element(" + i + "): " + actual);
      }
      i++;
    }
  }

  private static <T> Throwable accepts(final Consumer<T> condition, final T actual) {
    try {
      condition.consume(actual);
      return null;
    } catch (Throwable e) {
      return e;
    }
  }

  public static <T> T assertInstanceOf(Object o, Class<T> aClass) {
    Assert.assertNotNull("Expected instance of: " + aClass.getName() + " actual: " + null, o);
    Assert.assertTrue(
        "Expected instance of: " + aClass.getName() + " actual: " + o.getClass().getName(),
        aClass.isInstance(o));
    @SuppressWarnings("unchecked")
    T t = (T) o;
    return t;
  }

  public static <T> T assertOneElement(Collection<T> collection) {
    Assert.assertNotNull(collection);
    Assert.assertEquals(toString(collection), 1, collection.size());
    return collection.iterator().next();
  }

  public static <T> T assertOneElement(T[] ts) {
    Assert.assertNotNull(ts);
    Assert.assertEquals(Arrays.asList(ts).toString(), 1, ts.length);
    return ts[0];
  }

  public static <T> void assertOneOf(T value, T... values) {
    boolean found = false;
    for (T v : values) {
      if (value == v || value != null && value.equals(v)) {
        found = true;
      }
    }
    Assert.assertTrue(value + " should be equal to one of " + Arrays.toString(values), found);
  }

  public static void printThreadDump() {
    PerformanceWatcher.dumpThreadsToConsole("Thread dump:");
  }

  public static void assertEmpty(final Object[] array) {
    assertOrderedEquals(array);
  }

  public static void assertNotEmpty(final Collection<?> collection) {
    if (collection == null) return;
    assertTrue(!collection.isEmpty());
  }

  public static void assertEmpty(final Collection<?> collection) {
    assertEmpty(collection.toString(), collection);
  }

  public static void assertNullOrEmpty(final Collection<?> collection) {
    if (collection == null) return;
    assertEmpty(null, collection);
  }

  public static void assertEmpty(final String s) {
    assertTrue(s, StringUtil.isEmpty(s));
  }

  public static <T> void assertEmpty(final String errorMsg, final Collection<T> collection) {
    assertOrderedEquals(errorMsg, collection);
  }

  public static void assertSize(int expectedSize, final Object[] array) {
    assertEquals(toString(Arrays.asList(array)), expectedSize, array.length);
  }

  public static void assertSize(int expectedSize, final Collection<?> c) {
    assertEquals(toString(c), expectedSize, c.size());
  }

  protected <T extends Disposable> T disposeOnTearDown(final T disposable) {
    Disposer.register(myTestRootDisposable, disposable);
    return disposable;
  }

  public static void assertSameLines(String expected, String actual) {
    String expectedText = StringUtil.convertLineSeparators(expected.trim());
    String actualText = StringUtil.convertLineSeparators(actual.trim());
    Assert.assertEquals(expectedText, actualText);
  }

  public static void assertExists(File file) {
    assertTrue("File should exist " + file, file.exists());
  }

  public static void assertDoesntExist(File file) {
    assertFalse("File should not exist " + file, file.exists());
  }

  protected String getTestName(boolean lowercaseFirstLetter) {
    String name = getName();
    return getTestName(name, lowercaseFirstLetter);
  }

  public static String getTestName(String name, boolean lowercaseFirstLetter) {
    if (name == null) {
      return "";
    }
    name = StringUtil.trimStart(name, "test");
    if (StringUtil.isEmpty(name)) {
      return "";
    }
    return lowercaseFirstLetter(name, lowercaseFirstLetter);
  }

  public static String lowercaseFirstLetter(String name, boolean lowercaseFirstLetter) {
    if (lowercaseFirstLetter && !isAllUppercaseName(name)) {
      name = Character.toLowerCase(name.charAt(0)) + name.substring(1);
    }
    return name;
  }

  public static boolean isAllUppercaseName(String name) {
    int uppercaseChars = 0;
    for (int i = 0; i < name.length(); i++) {
      if (Character.isLowerCase(name.charAt(i))) {
        return false;
      }
      if (Character.isUpperCase(name.charAt(i))) {
        uppercaseChars++;
      }
    }
    return uppercaseChars >= 3;
  }

  protected String getTestDirectoryName() {
    final String testName = getTestName(true);
    return testName.replaceAll("_.*", "");
  }

  protected static void assertSameLinesWithFile(String filePath, String actualText) {
    String fileText;
    try {
      if (OVERWRITE_TESTDATA) {
        VfsTestUtil.overwriteTestData(filePath, actualText);
        System.out.println("File " + filePath + " created.");
      }
      fileText = FileUtil.loadFile(new File(filePath), CharsetToolkit.UTF8);
    } catch (FileNotFoundException e) {
      VfsTestUtil.overwriteTestData(filePath, actualText);
      throw new AssertionFailedError("No output text found. File " + filePath + " created.");
    } catch (IOException e) {
      throw new RuntimeException(e);
    }
    String expected = StringUtil.convertLineSeparators(fileText.trim());
    String actual = StringUtil.convertLineSeparators(actualText.trim());
    if (!Comparing.equal(expected, actual)) {
      throw new FileComparisonFailure(null, expected, actual, filePath);
    }
  }

  public static void clearFields(final Object test) throws IllegalAccessException {
    Class aClass = test.getClass();
    while (aClass != null) {
      clearDeclaredFields(test, aClass);
      aClass = aClass.getSuperclass();
    }
  }

  public static void clearDeclaredFields(Object test, Class aClass) throws IllegalAccessException {
    if (aClass == null) return;
    for (final Field field : aClass.getDeclaredFields()) {
      @NonNls final String name = field.getDeclaringClass().getName();
      if (!name.startsWith("junit.framework.") && !name.startsWith("com.intellij.testFramework.")) {
        final int modifiers = field.getModifiers();
        if ((modifiers & Modifier.FINAL) == 0
            && (modifiers & Modifier.STATIC) == 0
            && !field.getType().isPrimitive()) {
          field.setAccessible(true);
          field.set(test, null);
        }
      }
    }
  }

  @SuppressWarnings("deprecation")
  protected static void checkSettingsEqual(
      JDOMExternalizable expected, JDOMExternalizable settings, String message) throws Exception {
    if (expected == null || settings == null) return;

    Element oldS = new Element("temp");
    expected.writeExternal(oldS);
    Element newS = new Element("temp");
    settings.writeExternal(newS);

    String newString = JDOMUtil.writeElement(newS, "\n");
    String oldString = JDOMUtil.writeElement(oldS, "\n");
    Assert.assertEquals(message, oldString, newString);
  }

  public boolean isPerformanceTest() {
    String name = getName();
    return name != null && name.contains("Performance")
        || getClass().getName().contains("Performance");
  }

  public static void doPostponedFormatting(final Project project) {
    DocumentUtil.writeInRunUndoTransparentAction(
        new Runnable() {
          @Override
          public void run() {
            PsiDocumentManager.getInstance(project).commitAllDocuments();
            PostprocessReformattingAspect.getInstance(project).doPostponedFormatting();
          }
        });
  }

  protected static void checkAllTimersAreDisposed() {
    try {
      Class<?> aClass = Class.forName("javax.swing.TimerQueue");

      Method inst = aClass.getDeclaredMethod("sharedInstance");
      inst.setAccessible(true);
      Object queue = inst.invoke(null);
      Field field = aClass.getDeclaredField("firstTimer");
      field.setAccessible(true);
      Object firstTimer = field.get(queue);
      if (firstTimer != null) {
        try {
          fail("Not disposed Timer: " + firstTimer.toString() + "; queue:" + queue);
        } finally {
          field.set(queue, null);
        }
      }
    } catch (Throwable e) {
      // Ignore
    }
  }

  /**
   * Checks that code block throw corresponding exception.
   *
   * @param exceptionCase Block annotated with some exception type
   * @throws Throwable
   */
  protected void assertException(final AbstractExceptionCase exceptionCase) throws Throwable {
    assertException(exceptionCase, null);
  }

  /**
   * Checks that code block throw corresponding exception with expected error msg. If expected error
   * message is null it will not be checked.
   *
   * @param exceptionCase Block annotated with some exception type
   * @param expectedErrorMsg expected error messge
   * @throws Throwable
   */
  protected void assertException(
      final AbstractExceptionCase exceptionCase, @Nullable final String expectedErrorMsg)
      throws Throwable {
    assertExceptionOccurred(true, exceptionCase, expectedErrorMsg);
  }

  /**
   * Checks that code block doesn't throw corresponding exception.
   *
   * @param exceptionCase Block annotated with some exception type
   * @throws Throwable
   */
  protected void assertNoException(final AbstractExceptionCase exceptionCase) throws Throwable {
    assertExceptionOccurred(false, exceptionCase, null);
  }

  protected void assertNoThrowable(final Runnable closure) {
    String throwableName = null;
    try {
      closure.run();
    } catch (Throwable thr) {
      throwableName = thr.getClass().getName();
    }
    assertNull(throwableName);
  }

  private static void assertExceptionOccurred(
      boolean shouldOccur, AbstractExceptionCase exceptionCase, String expectedErrorMsg)
      throws Throwable {
    boolean wasThrown = false;
    try {
      exceptionCase.tryClosure();
    } catch (Throwable e) {
      if (shouldOccur) {
        wasThrown = true;
        final String errorMessage = exceptionCase.getAssertionErrorMessage();
        assertEquals(errorMessage, exceptionCase.getExpectedExceptionClass(), e.getClass());
        if (expectedErrorMsg != null) {
          assertEquals("Compare error messages", expectedErrorMsg, e.getMessage());
        }
      } else if (exceptionCase.getExpectedExceptionClass().equals(e.getClass())) {
        wasThrown = true;

        System.out.println("");
        e.printStackTrace(System.out);

        fail("Exception isn't expected here. Exception message: " + e.getMessage());
      } else {
        throw e;
      }
    } finally {
      if (shouldOccur && !wasThrown) {
        fail(exceptionCase.getAssertionErrorMessage());
      }
    }
  }

  protected boolean annotatedWith(@NotNull Class annotationClass) {
    Class<?> aClass = getClass();
    String methodName = "test" + getTestName(false);
    boolean methodChecked = false;
    while (aClass != null && aClass != Object.class) {
      if (aClass.getAnnotation(annotationClass) != null) return true;
      if (!methodChecked) {
        try {
          Method method = aClass.getDeclaredMethod(methodName);
          if (method.getAnnotation(annotationClass) != null) return true;
          methodChecked = true;
        } catch (NoSuchMethodException ignored) {
        }
      }
      aClass = aClass.getSuperclass();
    }
    return false;
  }

  protected String getHomePath() {
    return PathManager.getHomePath().replace(File.separatorChar, '/');
  }

  protected static boolean isInHeadlessEnvironment() {
    return GraphicsEnvironment.isHeadless();
  }

  public static void refreshRecursively(@NotNull VirtualFile file) {
    VfsUtilCore.visitChildrenRecursively(
        file,
        new VirtualFileVisitor() {
          @Override
          public boolean visitFile(@NotNull VirtualFile file) {
            file.getChildren();
            return true;
          }
        });
    file.refresh(false, true);
  }

  @NotNull
  public static Test filteredSuite(@RegExp String regexp, @NotNull Test test) {
    final Pattern pattern = Pattern.compile(regexp);
    final TestSuite testSuite = new TestSuite();
    new Processor<Test>() {

      @Override
      public boolean process(Test test) {
        if (test instanceof TestSuite) {
          for (int i = 0, len = ((TestSuite) test).testCount(); i < len; i++) {
            process(((TestSuite) test).testAt(i));
          }
        } else if (pattern.matcher(test.toString()).find()) {
          testSuite.addTest(test);
        }
        return false;
      }
    }.process(test);
    return testSuite;
  }
}
/** Author: msk */
public class EditorsSplitters extends IdePanePanel implements UISettingsListener, Disposable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.openapi.fileEditor.impl.EditorsSplitters");
  private static final String PINNED = "pinned";
  private static final String CURRENT_IN_TAB = "current-in-tab";

  private static final Key<Object> DUMMY_KEY = Key.create("EditorsSplitters.dummy.key");

  private EditorWindow myCurrentWindow;
  private final Set<EditorWindow> myWindows = new CopyOnWriteArraySet<EditorWindow>();

  private final FileEditorManagerImpl myManager;
  private Element mySplittersElement; // temporarily used during initialization
  int myInsideChange;
  private final MyFocusWatcher myFocusWatcher;
  private final Alarm myIconUpdaterAlarm = new Alarm();
  private final UIBuilder myUIBuilder = new UIBuilder();

  EditorsSplitters(
      final FileEditorManagerImpl manager,
      DockManager dockManager,
      boolean createOwnDockableContainer) {
    super(new BorderLayout());
    myManager = manager;
    myFocusWatcher = new MyFocusWatcher();
    setFocusTraversalPolicy(new MyFocusTraversalPolicy());
    clear();

    if (createOwnDockableContainer) {
      DockableEditorTabbedContainer dockable =
          new DockableEditorTabbedContainer(myManager.getProject(), this, false);
      Disposer.register(manager.getProject(), dockable);
      dockManager.register(dockable);
    }
    KeymapManagerListener keymapListener =
        new KeymapManagerListener() {
          @Override
          public void activeKeymapChanged(Keymap keymap) {
            invalidate();
            repaint();
          }
        };
    KeymapManager.getInstance().addKeymapManagerListener(keymapListener, this);
  }

  public FileEditorManagerImpl getManager() {
    return myManager;
  }

  public void clear() {
    for (EditorWindow window : myWindows) {
      window.dispose();
    }
    removeAll();
    myWindows.clear();
    setCurrentWindow(null);
    repaint(); // revalidate doesn't repaint correctly after "Close All"
  }

  void startListeningFocus() {
    myFocusWatcher.install(this);
  }

  private void stopListeningFocus() {
    myFocusWatcher.deinstall(this);
  }

  @Override
  public void dispose() {
    myIconUpdaterAlarm.cancelAllRequests();
    stopListeningFocus();
  }

  @Nullable
  public VirtualFile getCurrentFile() {
    if (myCurrentWindow != null) {
      return myCurrentWindow.getSelectedFile();
    }
    return null;
  }

  private boolean showEmptyText() {
    return myCurrentWindow == null || myCurrentWindow.getFiles().length == 0;
  }

  @Override
  protected void paintComponent(Graphics g) {
    if (showEmptyText()) {
      Graphics2D gg = IdeBackgroundUtil.withFrameBackground(g, this);
      super.paintComponent(gg);
      g.setColor(UIUtil.isUnderDarcula() ? UIUtil.getBorderColor() : new Color(0, 0, 0, 50));
      g.drawLine(0, 0, getWidth(), 0);
    }
  }

  public void writeExternal(final Element element) {
    if (getComponentCount() != 0) {
      final Component comp = getComponent(0);
      LOG.assertTrue(comp instanceof JPanel);
      final JPanel panel = (JPanel) comp;
      if (panel.getComponentCount() != 0) {
        element.addContent(writePanel(panel));
      }
    }
  }

  @SuppressWarnings("HardCodedStringLiteral")
  private Element writePanel(final JPanel panel) {
    final Component comp = panel.getComponent(0);
    if (comp instanceof Splitter) {
      final Splitter splitter = (Splitter) comp;
      final Element res = new Element("splitter");
      res.setAttribute("split-orientation", splitter.getOrientation() ? "vertical" : "horizontal");
      res.setAttribute("split-proportion", Float.toString(splitter.getProportion()));
      final Element first = new Element("split-first");
      first.addContent(writePanel((JPanel) splitter.getFirstComponent()));
      final Element second = new Element("split-second");
      second.addContent(writePanel((JPanel) splitter.getSecondComponent()));
      res.addContent(first);
      res.addContent(second);
      return res;
    } else if (comp instanceof JBTabs) {
      final Element res = new Element("leaf");
      Integer limit =
          UIUtil.getClientProperty(
              ((JBTabs) comp).getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY);
      if (limit != null) {
        res.setAttribute(JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(), String.valueOf(limit));
      }

      writeWindow(res, findWindowWith(comp));
      return res;
    } else if (comp instanceof EditorWindow.TCompForTablessMode) {
      EditorWithProviderComposite composite = ((EditorWindow.TCompForTablessMode) comp).myEditor;
      Element res = new Element("leaf");
      res.addContent(writeComposite(composite.getFile(), composite, false, composite));
      return res;
    } else {
      LOG.error(comp != null ? comp.getClass().getName() : null);
      return null;
    }
  }

  private void writeWindow(@NotNull Element res, @Nullable EditorWindow window) {
    if (window != null) {
      EditorWithProviderComposite[] composites = window.getEditors();
      for (int i = 0; i < composites.length; i++) {
        VirtualFile file = window.getFileAt(i);
        res.addContent(
            writeComposite(
                file, composites[i], window.isFilePinned(file), window.getSelectedEditor()));
      }
    }
  }

  @NotNull
  private Element writeComposite(
      VirtualFile file,
      EditorWithProviderComposite composite,
      boolean pinned,
      EditorWithProviderComposite selectedEditor) {
    Element fileElement = new Element("file");
    fileElement.setAttribute("leaf-file-name", file.getName()); // TODO: all files
    composite.currentStateAsHistoryEntry().writeExternal(fileElement, getManager().getProject());
    fileElement.setAttribute(PINNED, Boolean.toString(pinned));
    fileElement.setAttribute(CURRENT_IN_TAB, Boolean.toString(composite.equals(selectedEditor)));
    return fileElement;
  }

  public void openFiles() {
    if (mySplittersElement != null) {
      initializeProgress();
      final JPanel comp = myUIBuilder.process(mySplittersElement, getTopPanel());
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              if (comp != null) {
                removeAll();
                add(comp, BorderLayout.CENTER);
                mySplittersElement = null;
              }
              // clear empty splitters
              for (EditorWindow window : getWindows()) {
                if (window.getEditors().length == 0) {
                  for (EditorWindow sibling : window.findSiblings()) {
                    sibling.unsplit(false);
                  }
                }
              }
            }
          });
    }
  }

  private static void initializeProgress() {
    ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
    if (indicator != null) {
      indicator.setText(IdeBundle.message("loading.editors"));
    }
  }

  public int getEditorsCount() {
    return mySplittersElement == null ? 0 : countFiles(mySplittersElement);
  }

  private double myProgressStep;

  public void setProgressStep(double step) {
    myProgressStep = step;
  }

  private void updateProgress() {
    ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
    if (indicator != null) {
      indicator.setFraction(indicator.getFraction() + myProgressStep);
    }
  }

  private static int countFiles(Element element) {
    Integer value =
        new ConfigTreeReader<Integer>() {
          @Override
          protected Integer processFiles(
              @NotNull List<Element> fileElements, @Nullable Integer context) {
            return fileElements.size();
          }

          @Override
          protected Integer processSplitter(
              @NotNull Element element,
              @Nullable Element firstChild,
              @Nullable Element secondChild,
              @Nullable Integer context) {
            Integer first = process(firstChild, null);
            Integer second = process(secondChild, null);
            return (first == null ? 0 : first) + (second == null ? 0 : second);
          }
        }.process(element, null);
    return value == null ? 0 : value;
  }

  public void readExternal(final Element element) {
    mySplittersElement = element;
  }

  @NotNull
  public VirtualFile[] getOpenFiles() {
    final Set<VirtualFile> files = new ArrayListSet<VirtualFile>();
    for (final EditorWindow myWindow : myWindows) {
      final EditorWithProviderComposite[] editors = myWindow.getEditors();
      for (final EditorWithProviderComposite editor : editors) {
        VirtualFile file = editor.getFile();
        // background thread may call this method when invalid file is being removed
        // do not return it here as it will quietly drop out soon
        if (file.isValid()) {
          files.add(file);
        }
      }
    }
    return VfsUtilCore.toVirtualFileArray(files);
  }

  @NotNull
  public VirtualFile[] getSelectedFiles() {
    final Set<VirtualFile> files = new ArrayListSet<VirtualFile>();
    for (final EditorWindow window : myWindows) {
      final VirtualFile file = window.getSelectedFile();
      if (file != null) {
        files.add(file);
      }
    }
    final VirtualFile[] virtualFiles = VfsUtilCore.toVirtualFileArray(files);
    final VirtualFile currentFile = getCurrentFile();
    if (currentFile != null) {
      for (int i = 0; i != virtualFiles.length; ++i) {
        if (Comparing.equal(virtualFiles[i], currentFile)) {
          virtualFiles[i] = virtualFiles[0];
          virtualFiles[0] = currentFile;
          break;
        }
      }
    }
    return virtualFiles;
  }

  @NotNull
  public FileEditor[] getSelectedEditors() {
    List<FileEditor> editors = new ArrayList<FileEditor>();
    Set<EditorWindow> windows = new THashSet<EditorWindow>(myWindows);
    final EditorWindow currentWindow = getCurrentWindow();
    if (currentWindow != null) {
      windows.add(currentWindow);
    }
    for (final EditorWindow window : windows) {
      final EditorWithProviderComposite composite = window.getSelectedEditor();
      if (composite != null) {
        editors.add(composite.getSelectedEditor());
      }
    }
    return editors.toArray(new FileEditor[editors.size()]);
  }

  public void updateFileIcon(@NotNull final VirtualFile file) {
    updateFileIconLater(file);
  }

  private void updateFileIconImmediately(final VirtualFile file) {
    final Collection<EditorWindow> windows = findWindows(file);
    for (EditorWindow window : windows) {
      window.updateFileIcon(file);
    }
  }

  private final Set<VirtualFile> myFilesToUpdateIconsFor = new HashSet<VirtualFile>();

  private void updateFileIconLater(VirtualFile file) {
    myFilesToUpdateIconsFor.add(file);
    myIconUpdaterAlarm.cancelAllRequests();
    myIconUpdaterAlarm.addRequest(
        () -> {
          if (myManager.getProject().isDisposed()) return;
          for (VirtualFile file1 : myFilesToUpdateIconsFor) {
            updateFileIconImmediately(file1);
          }
          myFilesToUpdateIconsFor.clear();
        },
        200,
        ModalityState.stateForComponent(this));
  }

  void updateFileColor(@NotNull final VirtualFile file) {
    final Collection<EditorWindow> windows = findWindows(file);
    for (final EditorWindow window : windows) {
      final int index = window.findEditorIndex(window.findFileComposite(file));
      LOG.assertTrue(index != -1);
      window.setForegroundAt(index, getManager().getFileColor(file));
      window.setWaveColor(index, getManager().isProblem(file) ? JBColor.red : null);
    }
  }

  public void trimToSize(final int editor_tab_limit) {
    for (final EditorWindow window : myWindows) {
      window.trimToSize(editor_tab_limit, null, true);
    }
  }

  public void setTabsPlacement(final int tabPlacement) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].setTabsPlacement(tabPlacement);
    }
  }

  void setTabLayoutPolicy(int scrollTabLayout) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].setTabLayoutPolicy(scrollTabLayout);
    }
  }

  void updateFileName(@Nullable final VirtualFile updatedFile) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      for (VirtualFile file : windows[i].getFiles()) {
        if (updatedFile == null || file.getName().equals(updatedFile.getName())) {
          windows[i].updateFileName(file);
        }
      }
    }

    Project project = myManager.getProject();

    final IdeFrame frame = getFrame(project);
    if (frame != null) {
      VirtualFile file = getCurrentFile();

      File ioFile = file == null ? null : new File(file.getPresentableUrl());
      String fileTitle = null;
      if (file != null) {
        fileTitle =
            DumbService.isDumb(project)
                ? file.getName()
                : FrameTitleBuilder.getInstance().getFileTitle(project, file);
      }

      frame.setFileTitle(fileTitle, ioFile);
    }
  }

  protected IdeFrame getFrame(Project project) {
    final WindowManagerEx windowManagerEx = WindowManagerEx.getInstanceEx();
    final IdeFrame frame = windowManagerEx.getFrame(project);
    LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || frame != null);
    return frame;
  }

  boolean isInsideChange() {
    return myInsideChange > 0;
  }

  private void setCurrentWindow(@Nullable final EditorWindow currentWindow) {
    if (currentWindow != null && !myWindows.contains(currentWindow)) {
      throw new IllegalArgumentException(currentWindow + " is not a member of this container");
    }
    myCurrentWindow = currentWindow;
  }

  void updateFileBackgroundColor(@NotNull VirtualFile file) {
    final EditorWindow[] windows = getWindows();
    for (int i = 0; i != windows.length; ++i) {
      windows[i].updateFileBackgroundColor(file);
    }
  }

  int getSplitCount() {
    if (getComponentCount() > 0) {
      JPanel panel = (JPanel) getComponent(0);
      return getSplitCount(panel);
    }
    return 0;
  }

  private static int getSplitCount(JComponent component) {
    if (component.getComponentCount() > 0) {
      final JComponent firstChild = (JComponent) component.getComponent(0);
      if (firstChild instanceof Splitter) {
        final Splitter splitter = (Splitter) firstChild;
        return getSplitCount(splitter.getFirstComponent())
            + getSplitCount(splitter.getSecondComponent());
      }
      return 1;
    }
    return 0;
  }

  protected void afterFileClosed(VirtualFile file) {}

  protected void afterFileOpen(VirtualFile file) {}

  @Nullable
  JBTabs getTabsAt(RelativePoint point) {
    Point thisPoint = point.getPoint(this);
    Component c = SwingUtilities.getDeepestComponentAt(this, thisPoint.x, thisPoint.y);
    while (c != null) {
      if (c instanceof JBTabs) {
        return (JBTabs) c;
      }
      c = c.getParent();
    }

    return null;
  }

  boolean isEmptyVisible() {
    EditorWindow[] windows = getWindows();
    for (EditorWindow each : windows) {
      if (!each.isEmptyVisible()) {
        return false;
      }
    }
    return true;
  }

  @Nullable
  private VirtualFile findNextFile(final VirtualFile file) {
    final EditorWindow[] windows = getWindows(); // TODO: use current file as base
    for (int i = 0; i != windows.length; ++i) {
      final VirtualFile[] files = windows[i].getFiles();
      for (final VirtualFile fileAt : files) {
        if (!Comparing.equal(fileAt, file)) {
          return fileAt;
        }
      }
    }
    return null;
  }

  void closeFile(VirtualFile file, boolean moveFocus) {
    final List<EditorWindow> windows = findWindows(file);
    if (!windows.isEmpty()) {
      final VirtualFile nextFile = findNextFile(file);
      for (final EditorWindow window : windows) {
        LOG.assertTrue(window.getSelectedEditor() != null);
        window.closeFile(file, false, moveFocus);
        if (window.getTabCount() == 0 && nextFile != null && myManager.getProject().isOpen()) {
          EditorWithProviderComposite newComposite = myManager.newEditorComposite(nextFile);
          window.setEditor(newComposite, moveFocus); // newComposite can be null
        }
      }
      // cleanup windows with no tabs
      for (final EditorWindow window : windows) {
        if (window.isDisposed()) {
          // call to window.unsplit() which might make its sibling disposed
          continue;
        }
        if (window.getTabCount() == 0) {
          window.unsplit(false);
        }
      }
    }
  }

  @Override
  public void uiSettingsChanged(UISettings source) {
    if (!myManager.getProject().isOpen()) return;
    for (VirtualFile file : getOpenFiles()) {
      updateFileBackgroundColor(file);
      updateFileIcon(file);
      updateFileColor(file);
    }
  }

  private final class MyFocusTraversalPolicy extends IdeFocusTraversalPolicy {
    @Override
    public final Component getDefaultComponentImpl(final Container focusCycleRoot) {
      if (myCurrentWindow != null) {
        final EditorWithProviderComposite selectedEditor = myCurrentWindow.getSelectedEditor();
        if (selectedEditor != null) {
          return IdeFocusTraversalPolicy.getPreferredFocusedComponent(
              selectedEditor.getComponent(), this);
        }
      }
      return IdeFocusTraversalPolicy.getPreferredFocusedComponent(EditorsSplitters.this, this);
    }
  }

  @Nullable
  public JPanel getTopPanel() {
    return getComponentCount() > 0 ? (JPanel) getComponent(0) : null;
  }

  public EditorWindow getCurrentWindow() {
    return myCurrentWindow;
  }

  public EditorWindow getOrCreateCurrentWindow(final VirtualFile file) {
    final List<EditorWindow> windows = findWindows(file);
    if (getCurrentWindow() == null) {
      final Iterator<EditorWindow> iterator = myWindows.iterator();
      if (!windows.isEmpty()) {
        setCurrentWindow(windows.get(0), false);
      } else if (iterator.hasNext()) {
        setCurrentWindow(iterator.next(), false);
      } else {
        createCurrentWindow();
      }
    } else if (!windows.isEmpty()) {
      if (!windows.contains(getCurrentWindow())) {
        setCurrentWindow(windows.get(0), false);
      }
    }
    return getCurrentWindow();
  }

  void createCurrentWindow() {
    LOG.assertTrue(myCurrentWindow == null);
    setCurrentWindow(createEditorWindow());
    add(myCurrentWindow.myPanel, BorderLayout.CENTER);
  }

  protected EditorWindow createEditorWindow() {
    return new EditorWindow(this);
  }

  /**
   * sets the window passed as a current ('focused') window among all splitters. All file openings
   * will be done inside this current window
   *
   * @param window a window to be set as current
   * @param requestFocus whether to request focus to the editor currently selected in this window
   */
  void setCurrentWindow(@Nullable final EditorWindow window, final boolean requestFocus) {
    final EditorWithProviderComposite newEditor =
        window == null ? null : window.getSelectedEditor();

    Runnable fireRunnable = () -> getManager().fireSelectionChanged(newEditor);

    setCurrentWindow(window);

    getManager().updateFileName(window == null ? null : window.getSelectedFile());

    if (window != null) {
      final EditorWithProviderComposite selectedEditor = window.getSelectedEditor();
      if (selectedEditor != null) {
        fireRunnable.run();
      }

      if (requestFocus) {
        window.requestFocus(true);
      }
    } else {
      fireRunnable.run();
    }
  }

  void addWindow(EditorWindow window) {
    myWindows.add(window);
  }

  void removeWindow(EditorWindow window) {
    myWindows.remove(window);
    if (myCurrentWindow == window) {
      myCurrentWindow = null;
    }
  }

  boolean containsWindow(EditorWindow window) {
    return myWindows.contains(window);
  }

  // ---------------------------------------------------------

  public EditorWithProviderComposite[] getEditorsComposites() {
    List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();

    for (final EditorWindow myWindow : myWindows) {
      final EditorWithProviderComposite[] editors = myWindow.getEditors();
      ContainerUtil.addAll(res, editors);
    }
    return res.toArray(new EditorWithProviderComposite[res.size()]);
  }

  // ---------------------------------------------------------

  @NotNull
  public List<EditorWithProviderComposite> findEditorComposites(@NotNull VirtualFile file) {
    List<EditorWithProviderComposite> res = new ArrayList<EditorWithProviderComposite>();
    for (final EditorWindow window : myWindows) {
      final EditorWithProviderComposite fileComposite = window.findFileComposite(file);
      if (fileComposite != null) {
        res.add(fileComposite);
      }
    }
    return res;
  }

  @NotNull
  private List<EditorWindow> findWindows(final VirtualFile file) {
    List<EditorWindow> res = new ArrayList<EditorWindow>();
    for (final EditorWindow window : myWindows) {
      if (window.findFileComposite(file) != null) {
        res.add(window);
      }
    }
    return res;
  }

  @NotNull
  public EditorWindow[] getWindows() {
    return myWindows.toArray(new EditorWindow[myWindows.size()]);
  }

  @NotNull
  EditorWindow[] getOrderedWindows() {
    final List<EditorWindow> res = new ArrayList<EditorWindow>();

    // Collector for windows in tree ordering:
    class Inner {
      private void collect(final JPanel panel) {
        final Component comp = panel.getComponent(0);
        if (comp instanceof Splitter) {
          final Splitter splitter = (Splitter) comp;
          collect((JPanel) splitter.getFirstComponent());
          collect((JPanel) splitter.getSecondComponent());
        } else if (comp instanceof JPanel || comp instanceof JBTabs) {
          final EditorWindow window = findWindowWith(comp);
          if (window != null) {
            res.add(window);
          }
        }
      }
    }

    // get root component and traverse splitters tree:
    if (getComponentCount() != 0) {
      final Component comp = getComponent(0);
      LOG.assertTrue(comp instanceof JPanel);
      final JPanel panel = (JPanel) comp;
      if (panel.getComponentCount() != 0) {
        new Inner().collect(panel);
      }
    }

    LOG.assertTrue(res.size() == myWindows.size());
    return res.toArray(new EditorWindow[res.size()]);
  }

  @Nullable
  private EditorWindow findWindowWith(final Component component) {
    if (component != null) {
      for (final EditorWindow window : myWindows) {
        if (SwingUtilities.isDescendingFrom(component, window.myPanel)) {
          return window;
        }
      }
    }
    return null;
  }

  public boolean isFloating() {
    return false;
  }

  public boolean isPreview() {
    return false;
  }

  private final class MyFocusWatcher extends FocusWatcher {
    @Override
    protected void focusedComponentChanged(final Component component, final AWTEvent cause) {
      EditorWindow newWindow = null;

      if (component != null) {
        newWindow = findWindowWith(component);
      } else if (cause instanceof ContainerEvent
          && cause.getID() == ContainerEvent.COMPONENT_REMOVED) {
        // do not change current window in case of child removal as in JTable.removeEditor
        // otherwise Escape in a toolwindow will not focus editor with JTable content
        return;
      }

      setCurrentWindow(newWindow);
      setCurrentWindow(newWindow, false);
    }
  }

  private abstract static class ConfigTreeReader<T> {
    @Nullable
    public T process(@Nullable Element element, @Nullable T context) {
      if (element == null) {
        return null;
      }
      final Element splitterElement = element.getChild("splitter");
      if (splitterElement != null) {
        final Element first = splitterElement.getChild("split-first");
        final Element second = splitterElement.getChild("split-second");
        return processSplitter(splitterElement, first, second, context);
      }

      final Element leaf = element.getChild("leaf");
      if (leaf == null) {
        return null;
      }

      List<Element> fileElements = leaf.getChildren("file");
      final List<Element> children = new ArrayList<Element>(fileElements.size());

      // trim to EDITOR_TAB_LIMIT, ignoring CLOSE_NON_MODIFIED_FILES_FIRST policy
      int toRemove = fileElements.size() - UISettings.getInstance().EDITOR_TAB_LIMIT;
      for (Element fileElement : fileElements) {
        if (toRemove <= 0
            || Boolean.valueOf(fileElement.getAttributeValue(PINNED)).booleanValue()) {
          children.add(fileElement);
        } else {
          toRemove--;
        }
      }

      return processFiles(children, context);
    }

    @Nullable
    protected abstract T processFiles(@NotNull List<Element> fileElements, @Nullable T context);

    @Nullable
    protected abstract T processSplitter(
        @NotNull Element element,
        @Nullable Element firstChild,
        @Nullable Element secondChild,
        @Nullable T context);
  }

  private class UIBuilder extends ConfigTreeReader<JPanel> {

    @Override
    protected JPanel processFiles(@NotNull List<Element> fileElements, final JPanel context) {
      final Ref<EditorWindow> windowRef = new Ref<EditorWindow>();
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              windowRef.set(context == null ? createEditorWindow() : findWindowWith(context));
            }
          });
      final EditorWindow window = windowRef.get();
      LOG.assertTrue(window != null);
      VirtualFile focusedFile = null;

      for (int i = 0; i < fileElements.size(); i++) {
        final Element file = fileElements.get(i);
        if (i == 0) {
          EditorTabbedContainer tabbedPane = window.getTabbedPane();
          if (tabbedPane != null) {
            try {
              int limit =
                  Integer.parseInt(
                      file.getParentElement()
                          .getAttributeValue(
                              JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY.toString(),
                              String.valueOf(JBTabsImpl.DEFAULT_MAX_TAB_WIDTH)));
              UIUtil.putClientProperty(
                  tabbedPane.getComponent(), JBTabsImpl.SIDE_TABS_SIZE_LIMIT_KEY, limit);
            } catch (NumberFormatException e) {
              // ignore
            }
          }
        }
        try {
          final FileEditorManagerImpl fileEditorManager = getManager();
          Element historyElement = file.getChild(HistoryEntry.TAG);
          final HistoryEntry entry =
              HistoryEntry.createLight(fileEditorManager.getProject(), historyElement);
          final VirtualFile virtualFile = entry.getFile();
          if (virtualFile == null)
            throw new InvalidDataException("No file exists: " + entry.getFilePointer().getUrl());
          Document document =
              ApplicationManager.getApplication()
                  .runReadAction(
                      new Computable<Document>() {
                        @Override
                        public Document compute() {
                          return virtualFile.isValid()
                              ? FileDocumentManager.getInstance().getDocument(virtualFile)
                              : null;
                        }
                      });
          final boolean isCurrentInTab =
              Boolean.valueOf(file.getAttributeValue(CURRENT_IN_TAB)).booleanValue();
          Boolean pin = Boolean.valueOf(file.getAttributeValue(PINNED));
          fileEditorManager.openFileImpl4(
              window, virtualFile, entry, isCurrentInTab, isCurrentInTab, pin, i);
          if (isCurrentInTab) {
            focusedFile = virtualFile;
          }
          if (document != null) {
            // This is just to make sure document reference is kept on stack till this point
            // so that document is available for folding state deserialization in HistoryEntry
            // constructor
            // and that document will be created only once during file opening
            document.putUserData(DUMMY_KEY, null);
          }
          updateProgress();
        } catch (InvalidDataException e) {
          if (ApplicationManager.getApplication().isUnitTestMode()) {
            LOG.error(e);
          }
        }
      }
      if (focusedFile != null) {
        getManager().addSelectionRecord(focusedFile, window);
      }
      return window.myPanel;
    }

    @Override
    protected JPanel processSplitter(
        @NotNull Element splitterElement,
        Element firstChild,
        Element secondChild,
        final JPanel context) {
      if (context == null) {
        final boolean orientation =
            "vertical".equals(splitterElement.getAttributeValue("split-orientation"));
        final float proportion =
            Float.valueOf(splitterElement.getAttributeValue("split-proportion")).floatValue();
        final JPanel firstComponent = process(firstChild, null);
        final JPanel secondComponent = process(secondChild, null);
        final Ref<JPanel> panelRef = new Ref<JPanel>();
        UIUtil.invokeAndWaitIfNeeded(
            new Runnable() {
              @Override
              public void run() {
                JPanel panel = new JPanel(new BorderLayout());
                panel.setOpaque(false);
                Splitter splitter = new OnePixelSplitter(orientation, proportion, 0.1f, 0.9f);
                panel.add(splitter, BorderLayout.CENTER);
                splitter.setFirstComponent(firstComponent);
                splitter.setSecondComponent(secondComponent);
                panelRef.set(panel);
              }
            });
        return panelRef.get();
      }
      final Ref<JPanel> firstComponent = new Ref<JPanel>();
      final Ref<JPanel> secondComponent = new Ref<JPanel>();
      UIUtil.invokeAndWaitIfNeeded(
          new Runnable() {
            @Override
            public void run() {
              if (context.getComponent(0) instanceof Splitter) {
                Splitter splitter = (Splitter) context.getComponent(0);
                firstComponent.set((JPanel) splitter.getFirstComponent());
                secondComponent.set((JPanel) splitter.getSecondComponent());
              } else {
                firstComponent.set(context);
                secondComponent.set(context);
              }
            }
          });
      process(firstChild, firstComponent.get());
      process(secondChild, secondComponent.get());
      return context;
    }
  }
}
public class GeneralHighlightingPass extends ProgressableTextEditorHighlightingPass
    implements DumbAware {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.codeInsight.daemon.impl.GeneralHighlightingPass");
  static final String PRESENTABLE_NAME = DaemonBundle.message("pass.syntax");
  private static final Key<Boolean> HAS_ERROR_ELEMENT = Key.create("HAS_ERROR_ELEMENT");

  private final int myStartOffset;
  private final int myEndOffset;
  private final boolean myUpdateAll;
  private final ProperTextRange myPriorityRange;
  private final Editor myEditor;

  private final List<HighlightInfo> myHighlights = new ArrayList<HighlightInfo>();

  protected volatile boolean myHasErrorElement;
  private volatile boolean myErrorFound;
  private static final Comparator<HighlightVisitor> VISITOR_ORDER_COMPARATOR =
      new Comparator<HighlightVisitor>() {
        @Override
        public int compare(final HighlightVisitor o1, final HighlightVisitor o2) {
          return o1.order() - o2.order();
        }
      };
  private Runnable myApplyCommand;
  private final EditorColorsScheme myGlobalScheme;
  private boolean myFailFastOnAcquireReadAction = true;

  public GeneralHighlightingPass(
      @NotNull Project project,
      @NotNull PsiFile file,
      @NotNull Document document,
      int startOffset,
      int endOffset,
      boolean updateAll) {
    this(
        project,
        file,
        document,
        startOffset,
        endOffset,
        updateAll,
        new ProperTextRange(0, document.getTextLength()),
        null);
  }

  public GeneralHighlightingPass(
      @NotNull Project project,
      @NotNull PsiFile file,
      @NotNull Document document,
      int startOffset,
      int endOffset,
      boolean updateAll,
      @NotNull ProperTextRange priorityRange,
      @Nullable Editor editor) {
    super(project, document, PRESENTABLE_NAME, file, true);
    myStartOffset = startOffset;
    myEndOffset = endOffset;
    myUpdateAll = updateAll;
    myPriorityRange = priorityRange;
    myEditor = editor;

    LOG.assertTrue(file.isValid());
    setId(Pass.UPDATE_ALL);
    myHasErrorElement =
        !isWholeFileHighlighting() && Boolean.TRUE.equals(myFile.getUserData(HAS_ERROR_ELEMENT));
    FileStatusMap fileStatusMap =
        ((DaemonCodeAnalyzerImpl) DaemonCodeAnalyzer.getInstance(myProject)).getFileStatusMap();
    myErrorFound = !isWholeFileHighlighting() && fileStatusMap.wasErrorFound(myDocument);

    myApplyCommand =
        new Runnable() {
          @Override
          public void run() {
            ProperTextRange range = new ProperTextRange(myStartOffset, myEndOffset);
            MarkupModel model = DocumentMarkupModel.forDocument(myDocument, myProject, true);
            UpdateHighlightersUtil.cleanFileLevelHighlights(myProject, Pass.UPDATE_ALL, myFile);
            final EditorColorsScheme colorsScheme = getColorsScheme();
            UpdateHighlightersUtil.setHighlightersInRange(
                myProject,
                myDocument,
                range,
                colorsScheme,
                myHighlights,
                (MarkupModelEx) model,
                Pass.UPDATE_ALL);
          }
        };

    // initial guess to show correct progress in the traffic light icon
    setProgressLimit(document.getTextLength() / 2); // approx number of PSI elements = file length/2
    myGlobalScheme = EditorColorsManager.getInstance().getGlobalScheme();
  }

  private static final Key<AtomicInteger> HIGHLIGHT_VISITOR_INSTANCE_COUNT =
      new Key<AtomicInteger>("HIGHLIGHT_VISITOR_INSTANCE_COUNT");

  @NotNull
  private HighlightVisitor[] getHighlightVisitors() {
    int oldCount = incVisitorUsageCount(1);
    HighlightVisitor[] highlightVisitors = createHighlightVisitors();
    if (oldCount != 0) {
      HighlightVisitor[] clones = new HighlightVisitor[highlightVisitors.length];
      for (int i = 0; i < highlightVisitors.length; i++) {
        HighlightVisitor highlightVisitor = highlightVisitors[i];
        clones[i] = highlightVisitor.clone();
      }
      highlightVisitors = clones;
    }
    return highlightVisitors;
  }

  protected HighlightVisitor[] createHighlightVisitors() {
    return Extensions.getExtensions(HighlightVisitor.EP_HIGHLIGHT_VISITOR, myProject);
  }

  // returns old value
  private int incVisitorUsageCount(int delta) {
    AtomicInteger count = myProject.getUserData(HIGHLIGHT_VISITOR_INSTANCE_COUNT);
    if (count == null) {
      count =
          ((UserDataHolderEx) myProject)
              .putUserDataIfAbsent(HIGHLIGHT_VISITOR_INSTANCE_COUNT, new AtomicInteger(0));
    }
    int old = count.getAndAdd(delta);
    assert old + delta >= 0 : old + ";" + delta;
    return old;
  }

  @Override
  protected void collectInformationWithProgress(final ProgressIndicator progress) {
    final Set<HighlightInfo> gotHighlights = new THashSet<HighlightInfo>(100);
    final Set<HighlightInfo> outsideResult = new THashSet<HighlightInfo>(100);

    DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
    HighlightVisitor[] highlightVisitors = getHighlightVisitors();
    final HighlightVisitor[] filteredVisitors = filterVisitors(highlightVisitors, myFile);
    final List<PsiElement> inside = new ArrayList<PsiElement>();
    final List<PsiElement> outside = new ArrayList<PsiElement>();
    try {
      Divider.divideInsideAndOutside(
          myFile,
          myStartOffset,
          myEndOffset,
          myPriorityRange,
          inside,
          outside,
          HighlightLevelUtil.AnalysisLevel.HIGHLIGHT,
          false);

      setProgressLimit((long) (inside.size() + outside.size()));

      final boolean forceHighlightParents = forceHighlightParents();

      if (!isDumbMode()) {
        highlightTodos(
            myFile,
            myDocument.getCharsSequence(),
            myStartOffset,
            myEndOffset,
            progress,
            myPriorityRange,
            gotHighlights,
            outsideResult);
      }

      collectHighlights(
          inside,
          new Runnable() {
            @Override
            public void run() {
              // all infos for the "injected fragment for the host which is inside" are indeed
              // inside
              // but some of the infos for the "injected fragment for the host which is outside" can
              // be still inside
              Set<HighlightInfo> injectedResult = new THashSet<HighlightInfo>();
              final Set<PsiFile> injected = new THashSet<PsiFile>();
              getInjectedPsiFiles(inside, outside, progress, injected);
              if (!addInjectedPsiHighlights(
                  injected, progress, Collections.synchronizedSet(injectedResult)))
                throw new ProcessCanceledException();
              final List<HighlightInfo> injectionsOutside =
                  new ArrayList<HighlightInfo>(gotHighlights.size());

              Set<HighlightInfo> result;
              synchronized (injectedResult) {
                // sync here because all writes happened in another thread
                result = injectedResult;
              }
              for (HighlightInfo info : result) {
                if (myPriorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) {
                  gotHighlights.add(info);
                } else {
                  // nonconditionally apply injected results regardless whether they are in
                  // myStartOffset,myEndOffset
                  injectionsOutside.add(info);
                }
              }

              if (outsideResult.isEmpty() && injectionsOutside.isEmpty()) {
                return; // apply only result (by default apply command) and only within inside
              }

              final ProperTextRange priorityIntersection =
                  myPriorityRange.intersection(new TextRange(myStartOffset, myEndOffset));
              if ((!inside.isEmpty() || !gotHighlights.isEmpty())
                  && priorityIntersection
                      != null) { // do not apply when there were no elements to highlight
                // clear infos found in visible area to avoid applying them twice
                final List<HighlightInfo> toApplyInside =
                    new ArrayList<HighlightInfo>(gotHighlights);
                myHighlights.addAll(toApplyInside);
                gotHighlights.clear();
                gotHighlights.addAll(outsideResult);
                final long modificationStamp = myDocument.getModificationStamp();
                UIUtil.invokeLaterIfNeeded(
                    new Runnable() {
                      @Override
                      public void run() {
                        if (myProject.isDisposed()
                            || modificationStamp != myDocument.getModificationStamp()) return;
                        MarkupModel markupModel =
                            DocumentMarkupModel.forDocument(myDocument, myProject, true);

                        UpdateHighlightersUtil.setHighlightersInRange(
                            myProject,
                            myDocument,
                            priorityIntersection,
                            getColorsScheme(),
                            toApplyInside,
                            (MarkupModelEx) markupModel,
                            Pass.UPDATE_ALL);
                        if (myEditor != null) {
                          new ShowAutoImportPass(myProject, myFile, myEditor)
                              .applyInformationToEditor();
                        }
                      }
                    });
              }

              myApplyCommand =
                  new Runnable() {
                    @Override
                    public void run() {
                      ProperTextRange range = new ProperTextRange(myStartOffset, myEndOffset);

                      List<HighlightInfo> toApply = new ArrayList<HighlightInfo>();
                      for (HighlightInfo info : gotHighlights) {
                        if (!range.containsRange(info.getStartOffset(), info.getEndOffset()))
                          continue;
                        if (!myPriorityRange.containsRange(
                            info.getStartOffset(), info.getEndOffset())) {
                          toApply.add(info);
                        }
                      }
                      toApply.addAll(injectionsOutside);

                      UpdateHighlightersUtil.setHighlightersOutsideRange(
                          myProject,
                          myDocument,
                          toApply,
                          getColorsScheme(),
                          myStartOffset,
                          myEndOffset,
                          myPriorityRange,
                          Pass.UPDATE_ALL);
                    }
                  };
            }
          },
          outside,
          progress,
          filteredVisitors,
          gotHighlights,
          forceHighlightParents);

      if (myUpdateAll) {
        ((DaemonCodeAnalyzerImpl) daemonCodeAnalyzer)
            .getFileStatusMap()
            .setErrorFoundFlag(myDocument, myErrorFound);
      }
    } finally {
      incVisitorUsageCount(-1);
    }
    myHighlights.addAll(gotHighlights);
  }

  private void getInjectedPsiFiles(
      @NotNull final List<PsiElement> elements1,
      @NotNull final List<PsiElement> elements2,
      @NotNull final ProgressIndicator progress,
      @NotNull final Set<PsiFile> outInjected) {
    List<DocumentWindow> injected = InjectedLanguageUtil.getCachedInjectedDocuments(myFile);
    Collection<PsiElement> hosts =
        new THashSet<PsiElement>(elements1.size() + elements2.size() + injected.size());

    // rehighlight all injected PSI regardless the range,
    // since change in one place can lead to invalidation of injected PSI in (completely) other
    // place.
    for (DocumentWindow documentRange : injected) {
      progress.checkCanceled();
      if (!documentRange.isValid()) continue;
      PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(documentRange);
      if (file == null) continue;
      PsiElement context = file.getContext();
      if (context != null
          && context.isValid()
          && !file.getProject().isDisposed()
          && (myUpdateAll
              || new ProperTextRange(myStartOffset, myEndOffset)
                  .intersects(context.getTextRange()))) {
        hosts.add(context);
      }
    }
    hosts.addAll(elements1);
    hosts.addAll(elements2);

    final PsiLanguageInjectionHost.InjectedPsiVisitor visitor =
        new PsiLanguageInjectionHost.InjectedPsiVisitor() {
          @Override
          public void visit(
              @NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) {
            synchronized (outInjected) {
              outInjected.add(injectedPsi);
            }
          }
        };
    if (!JobUtil.invokeConcurrentlyUnderProgress(
        new ArrayList<PsiElement>(hosts),
        progress,
        false,
        new Processor<PsiElement>() {
          @Override
          public boolean process(PsiElement element) {
            progress.checkCanceled();
            InjectedLanguageUtil.enumerate(element, myFile, false, visitor);
            return true;
          }
        })) throw new ProcessCanceledException();
  }

  // returns false if canceled
  private boolean addInjectedPsiHighlights(
      @NotNull final Set<PsiFile> injectedFiles,
      @NotNull final ProgressIndicator progress,
      @NotNull final Collection<HighlightInfo> outInfos) {
    if (injectedFiles.isEmpty()) return true;
    final InjectedLanguageManager injectedLanguageManager =
        InjectedLanguageManager.getInstance(myProject);
    final TextAttributes injectedAttributes =
        myGlobalScheme.getAttributes(EditorColors.INJECTED_LANGUAGE_FRAGMENT);

    return JobUtil.invokeConcurrentlyUnderProgress(
        new ArrayList<PsiFile>(injectedFiles),
        progress,
        myFailFastOnAcquireReadAction,
        new Processor<PsiFile>() {
          @Override
          public boolean process(final PsiFile injectedPsi) {
            DocumentWindow documentWindow =
                (DocumentWindow)
                    PsiDocumentManager.getInstance(myProject).getCachedDocument(injectedPsi);
            if (documentWindow == null) return true;
            Place places = InjectedLanguageUtil.getShreds(injectedPsi);
            for (PsiLanguageInjectionHost.Shred place : places) {
              TextRange textRange =
                  place.getRangeInsideHost().shiftRight(place.host.getTextRange().getStartOffset());
              if (textRange.isEmpty()) continue;
              String desc =
                  injectedPsi.getLanguage().getDisplayName() + ": " + injectedPsi.getText();
              HighlightInfo info =
                  HighlightInfo.createHighlightInfo(
                      HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT,
                      textRange,
                      null,
                      desc,
                      injectedAttributes);
              info.fromInjection = true;
              outInfos.add(info);
            }

            HighlightInfoHolder holder = createInfoHolder(injectedPsi);
            runHighlightVisitorsForInjected(injectedPsi, holder, progress);
            for (int i = 0; i < holder.size(); i++) {
              HighlightInfo info = holder.get(i);
              final int startOffset = documentWindow.injectedToHost(info.startOffset);
              final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset);
              addPatchedInfos(
                  info,
                  injectedPsi,
                  documentWindow,
                  injectedLanguageManager,
                  fixedTextRange,
                  outInfos);
            }
            holder.clear();
            highlightInjectedSyntax(injectedPsi, holder);
            for (int i = 0; i < holder.size(); i++) {
              HighlightInfo info = holder.get(i);
              final int startOffset = info.startOffset;
              final TextRange fixedTextRange = getFixedTextRange(documentWindow, startOffset);
              if (fixedTextRange == null) {
                info.fromInjection = true;
                outInfos.add(info);
              } else {
                HighlightInfo patched =
                    new HighlightInfo(
                        info.forcedTextAttributes,
                        info.forcedTextAttributesKey,
                        info.type,
                        fixedTextRange.getStartOffset(),
                        fixedTextRange.getEndOffset(),
                        info.description,
                        info.toolTip,
                        info.type.getSeverity(null),
                        info.isAfterEndOfLine,
                        null,
                        false);
                patched.fromInjection = true;
                outInfos.add(patched);
              }
            }

            if (!isDumbMode()) {
              List<HighlightInfo> todos = new ArrayList<HighlightInfo>();
              highlightTodos(
                  injectedPsi,
                  injectedPsi.getText(),
                  0,
                  injectedPsi.getTextLength(),
                  progress,
                  myPriorityRange,
                  todos,
                  todos);
              for (HighlightInfo info : todos) {
                addPatchedInfos(
                    info, injectedPsi, documentWindow, injectedLanguageManager, null, outInfos);
              }
            }
            return true;
          }
        });
  }

  private static TextRange getFixedTextRange(
      @NotNull DocumentWindow documentWindow, int startOffset) {
    final TextRange fixedTextRange;
    TextRange textRange = documentWindow.getHostRange(startOffset);
    if (textRange == null) {
      // todo[cdr] check this fix. prefix/suffix code annotation case
      textRange = findNearestTextRange(documentWindow, startOffset);
      final boolean isBefore = startOffset < textRange.getStartOffset();
      fixedTextRange =
          new ProperTextRange(
              isBefore ? textRange.getStartOffset() - 1 : textRange.getEndOffset(),
              isBefore ? textRange.getStartOffset() : textRange.getEndOffset() + 1);
    } else {
      fixedTextRange = null;
    }
    return fixedTextRange;
  }

  private static void addPatchedInfos(
      @NotNull HighlightInfo info,
      @NotNull PsiFile injectedPsi,
      @NotNull DocumentWindow documentWindow,
      @NotNull InjectedLanguageManager injectedLanguageManager,
      @Nullable TextRange fixedTextRange,
      @NotNull Collection<HighlightInfo> out) {
    ProperTextRange textRange = new ProperTextRange(info.startOffset, info.endOffset);
    List<TextRange> editables =
        injectedLanguageManager.intersectWithAllEditableFragments(injectedPsi, textRange);
    for (TextRange editable : editables) {
      TextRange hostRange =
          fixedTextRange == null ? documentWindow.injectedToHost(editable) : fixedTextRange;

      boolean isAfterEndOfLine = info.isAfterEndOfLine;
      if (isAfterEndOfLine) {
        // convert injected afterEndOfLine to either host' afterEndOfLine or not-afterEndOfLine
        // highlight of the injected fragment boundary
        int hostEndOffset = hostRange.getEndOffset();
        int lineNumber = documentWindow.getDelegate().getLineNumber(hostEndOffset);
        int hostLineEndOffset = documentWindow.getDelegate().getLineEndOffset(lineNumber);
        if (hostEndOffset < hostLineEndOffset) {
          // convert to non-afterEndOfLine
          isAfterEndOfLine = false;
          hostRange = new ProperTextRange(hostRange.getStartOffset(), hostEndOffset + 1);
        }
      }

      HighlightInfo patched =
          new HighlightInfo(
              info.forcedTextAttributes,
              info.forcedTextAttributesKey,
              info.type,
              hostRange.getStartOffset(),
              hostRange.getEndOffset(),
              info.description,
              info.toolTip,
              info.type.getSeverity(null),
              isAfterEndOfLine,
              null,
              false);
      patched.setHint(info.hasHint());
      patched.setGutterIconRenderer(info.getGutterIconRenderer());

      if (info.quickFixActionRanges != null) {
        for (Pair<HighlightInfo.IntentionActionDescriptor, TextRange> pair :
            info.quickFixActionRanges) {
          TextRange quickfixTextRange = pair.getSecond();
          List<TextRange> editableQF =
              injectedLanguageManager.intersectWithAllEditableFragments(
                  injectedPsi, quickfixTextRange);
          for (TextRange editableRange : editableQF) {
            HighlightInfo.IntentionActionDescriptor descriptor = pair.getFirst();
            if (patched.quickFixActionRanges == null)
              patched.quickFixActionRanges =
                  new ArrayList<Pair<HighlightInfo.IntentionActionDescriptor, TextRange>>();
            TextRange hostEditableRange = documentWindow.injectedToHost(editableRange);
            patched.quickFixActionRanges.add(Pair.create(descriptor, hostEditableRange));
          }
        }
      }
      patched.fromInjection = true;
      out.add(patched);
    }
  }

  // finds the first nearest text range
  private static TextRange findNearestTextRange(
      final DocumentWindow documentWindow, final int startOffset) {
    TextRange textRange = null;
    for (RangeMarker marker : documentWindow.getHostRanges()) {
      TextRange curRange = ProperTextRange.create(marker);
      if (curRange.getStartOffset() > startOffset && textRange != null) break;
      textRange = curRange;
    }
    assert textRange != null;
    return textRange;
  }

  private void runHighlightVisitorsForInjected(
      @NotNull PsiFile injectedPsi,
      @NotNull final HighlightInfoHolder holder,
      @NotNull final ProgressIndicator progress) {
    HighlightVisitor[] visitors = getHighlightVisitors();
    try {
      HighlightVisitor[] filtered = filterVisitors(visitors, injectedPsi);
      final List<PsiElement> elements =
          CollectHighlightsUtil.getElementsInRange(injectedPsi, 0, injectedPsi.getTextLength());
      for (final HighlightVisitor visitor : filtered) {
        visitor.analyze(
            injectedPsi,
            true,
            holder,
            new Runnable() {
              @Override
              public void run() {
                for (PsiElement element : elements) {
                  progress.checkCanceled();
                  visitor.visit(element);
                }
              }
            });
      }
    } finally {
      incVisitorUsageCount(-1);
    }
  }

  private void highlightInjectedSyntax(final PsiFile injectedPsi, HighlightInfoHolder holder) {
    List<Trinity<IElementType, PsiLanguageInjectionHost, TextRange>> tokens =
        InjectedLanguageUtil.getHighlightTokens(injectedPsi);
    if (tokens == null) return;

    final Language injectedLanguage = injectedPsi.getLanguage();
    Project project = injectedPsi.getProject();
    SyntaxHighlighter syntaxHighlighter =
        SyntaxHighlighterFactory.getSyntaxHighlighter(
            injectedLanguage, project, injectedPsi.getVirtualFile());
    final TextAttributes defaultAttrs = myGlobalScheme.getAttributes(HighlighterColors.TEXT);

    for (Trinity<IElementType, PsiLanguageInjectionHost, TextRange> token : tokens) {
      ProgressManager.checkCanceled();
      IElementType tokenType = token.getFirst();
      PsiLanguageInjectionHost injectionHost = token.getSecond();
      TextRange textRange = token.getThird();
      TextAttributesKey[] keys = syntaxHighlighter.getTokenHighlights(tokenType);
      if (textRange.getLength() == 0) continue;

      TextRange annRange = textRange.shiftRight(injectionHost.getTextRange().getStartOffset());
      // force attribute colors to override host' ones
      TextAttributes attributes = null;
      for (TextAttributesKey key : keys) {
        TextAttributes attrs2 = myGlobalScheme.getAttributes(key);
        if (attrs2 != null) {
          attributes = attributes == null ? attrs2 : TextAttributes.merge(attributes, attrs2);
        }
      }
      TextAttributes forcedAttributes;
      if (attributes == null || attributes.isEmpty() || attributes.equals(defaultAttrs)) {
        forcedAttributes = TextAttributes.ERASE_MARKER;
      } else {
        Color back =
            attributes.getBackgroundColor() == null
                ? myGlobalScheme.getDefaultBackground()
                : attributes.getBackgroundColor();
        Color fore =
            attributes.getForegroundColor() == null
                ? myGlobalScheme.getDefaultForeground()
                : attributes.getForegroundColor();
        forcedAttributes =
            new TextAttributes(
                fore,
                back,
                attributes.getEffectColor(),
                attributes.getEffectType(),
                attributes.getFontType());
      }

      HighlightInfo info =
          HighlightInfo.createHighlightInfo(
              HighlightInfoType.INJECTED_LANGUAGE_FRAGMENT, annRange, null, null, forcedAttributes);
      holder.add(info);
    }
  }

  private boolean isWholeFileHighlighting() {
    return myUpdateAll && myStartOffset == 0 && myEndOffset == myDocument.getTextLength();
  }

  @Override
  protected void applyInformationWithProgress() {
    myFile.putUserData(HAS_ERROR_ELEMENT, myHasErrorElement);

    myApplyCommand.run();

    if (myUpdateAll) {
      reportErrorsToWolf();
    }
  }

  @Override
  @NotNull
  public List<HighlightInfo> getInfos() {
    return new ArrayList<HighlightInfo>(myHighlights);
  }

  private void collectHighlights(
      @NotNull final List<PsiElement> elements1,
      @NotNull final Runnable after1,
      @NotNull final List<PsiElement> elements2,
      @NotNull final ProgressIndicator progress,
      @NotNull final HighlightVisitor[] visitors,
      @NotNull final Set<HighlightInfo> gotHighlights,
      final boolean forceHighlightParents) {
    final Set<PsiElement> skipParentsSet = new THashSet<PsiElement>();

    // TODO - add color scheme to holder
    final HighlightInfoHolder holder = createInfoHolder(myFile);

    final int chunkSize =
        Math.max(1, (elements1.size() + elements2.size()) / 100); // one percent precision is enough

    final Runnable action =
        new Runnable() {
          @Override
          public void run() {
            //noinspection unchecked
            boolean failed = false;
            for (List<PsiElement> elements : new List[] {elements1, elements2}) {
              int nextLimit = chunkSize;
              for (int i = 0; i < elements.size(); i++) {
                PsiElement element = elements.get(i);
                progress.checkCanceled();

                if (element != myFile
                    && !skipParentsSet.isEmpty()
                    && element.getFirstChild() != null
                    && skipParentsSet.contains(element)) {
                  skipParentsSet.add(element.getParent());
                  continue;
                }

                if (element instanceof PsiErrorElement) {
                  myHasErrorElement = true;
                }
                holder.clear();

                for (final HighlightVisitor visitor : visitors) {
                  try {
                    visitor.visit(element);
                  } catch (ProcessCanceledException e) {
                    throw e;
                  } catch (IndexNotReadyException e) {
                    throw e;
                  } catch (WolfTheProblemSolverImpl.HaveGotErrorException e) {
                    throw e;
                  } catch (Exception e) {
                    if (!failed) {
                      LOG.error(e);
                    }
                    failed = true;
                  }
                }

                if (i == nextLimit) {
                  advanceProgress(chunkSize);
                  nextLimit = i + chunkSize;
                }

                //noinspection ForLoopReplaceableByForEach
                for (int j = 0; j < holder.size(); j++) {
                  final HighlightInfo info = holder.get(j);
                  assert info != null;
                  // have to filter out already obtained highlights
                  if (!gotHighlights.add(info)) continue;
                  boolean isError = info.getSeverity() == HighlightSeverity.ERROR;
                  if (isError) {
                    if (!forceHighlightParents) {
                      skipParentsSet.add(element.getParent());
                    }
                    myErrorFound = true;
                  }
                  myTransferToEDTQueue.offer(Pair.create(info, progress));
                }
              }
              advanceProgress(elements.size() - (nextLimit - chunkSize));
              if (elements == elements1) after1.run();
            }
          }
        };

    analyzeByVisitors(progress, visitors, holder, 0, action);
  }

  // private void collectHighlights(@NotNull final List<PsiElement> elements1,
  //                               @NotNull final Runnable after1,
  //                               @NotNull final List<PsiElement> elements2,
  //                               @NotNull final ProgressIndicator progress,
  //                               @NotNull final HighlightVisitor[] visitors,
  //                               @NotNull final Set<HighlightInfo> gotHighlights,
  //                               final boolean forceHighlightParents) {
  //  final HighlightInfoHolder holder = createInfoHolder(myFile);
  //
  //  final int chunkSize = Math.max(1, (elements1.size()+elements2.size()) / 100); // one percent
  // precision is enough
  //
  //  final Runnable action = new Runnable() {
  //    public void run() {
  //      //noinspection unchecked
  //      boolean failed = false;
  //      PsiNodeTask task = createPsiTask(myFile);
  //      JobSchedulerImpl.submitTask(task);
  //
  //      for (List<PsiElement> elements : new List[]{elements1, elements2}) {
  //        int nextLimit = chunkSize;
  //        for (int i = 0; i < elements.size(); i++) {
  //          PsiElement element = elements.get(i);
  //          progress.checkCanceled();
  //
  //          if (element != myFile && !skipParentsSet.isEmpty() && element.getFirstChild() != null
  // && skipParentsSet.contains(element)) {
  //            skipParentsSet.add(element.getParent());
  //            continue;
  //          }
  //
  //          if (element instanceof PsiErrorElement) {
  //            myHasErrorElement = true;
  //          }
  //          kjlhkjh
  //          if (i == nextLimit) {
  //            advanceProgress(chunkSize);
  //            nextLimit = i + chunkSize;
  //          }
  //
  //        }
  //        advanceProgress(elements.size() - (nextLimit-chunkSize));
  //        if (elements == elements1) after1.run();
  //      }
  //    }
  //
  //    private PsiNodeTask createPsiTask(@NotNull PsiElement root) {
  //      return new PsiNodeTask(root) {
  //        @Override
  //        public void onEnter(@NotNull PsiElement element) {
  //          if (element instanceof PsiErrorElement) {
  //            myHasErrorElement = true;
  //          }
  //          super.onEnter(element);
  //        }
  //
  //        @Override
  //        protected PsiNodeTask forkNode(@NotNull PsiElement child) {
  //          return createPsiTask(child);
  //        }
  //
  //        @Override
  //        protected boolean highlight(PsiElement element) {
  //          holder.clear();
  //
  //          for (final HighlightVisitor visitor : visitors) {
  //            try {
  //              visitor.visit(element);
  //            }
  //            catch (ProcessCanceledException e) {
  //              throw e;
  //            }
  //            catch (IndexNotReadyException e) {
  //              throw e;
  //            }
  //            catch (WolfTheProblemSolverImpl.HaveGotErrorException e) {
  //              throw e;
  //            }
  //            catch (Exception e) {
  //                LOG.error(e);
  //            }
  //          }
  //
  //          //noinspection ForLoopReplaceableByForEach
  //          for (int j = 0; j < holder.size(); j++) {
  //            final HighlightInfo info = holder.get(j);
  //            assert info != null;
  //            // have to filter out already obtained highlights
  //            if (!gotHighlights.add(info)) continue;
  //            boolean isError = info.getSeverity() == HighlightSeverity.ERROR;
  //            if (isError) {
  //              if (!forceHighlightParents) {
  //                skipParentsSet.add(element.getParent());
  //              }
  //              myErrorFound = true;
  //            }
  //            myTransferToEDTQueue.offer(Pair.create(info, progress));
  //          }
  //          return true;
  //        }
  //      };
  //    }
  //  };
  //
  //  analyzeByVisitors(progress, visitors, holder, 0, action);
  // }

  private final Map<TextRange, RangeMarker> ranges2markersCache =
      new THashMap<TextRange, RangeMarker>();
  private final TransferToEDTQueue<Pair<HighlightInfo, ProgressIndicator>> myTransferToEDTQueue =
      new TransferToEDTQueue<Pair<HighlightInfo, ProgressIndicator>>(
          "Apply highlighting results",
          new Processor<Pair<HighlightInfo, ProgressIndicator>>() {
            @Override
            public boolean process(Pair<HighlightInfo, ProgressIndicator> pair) {
              ApplicationManager.getApplication().assertIsDispatchThread();
              ProgressIndicator indicator = pair.getSecond();
              if (indicator.isCanceled()) {
                return false;
              }
              HighlightInfo info = pair.getFirst();
              final EditorColorsScheme colorsScheme = getColorsScheme();
              UpdateHighlightersUtil.addHighlighterToEditorIncrementally(
                  myProject,
                  myDocument,
                  myFile,
                  myStartOffset,
                  myEndOffset,
                  info,
                  colorsScheme,
                  Pass.UPDATE_ALL,
                  ranges2markersCache);

              return true;
            }
          },
          myProject.getDisposed(),
          200);

  private void analyzeByVisitors(
      @NotNull final ProgressIndicator progress,
      @NotNull final HighlightVisitor[] visitors,
      @NotNull final HighlightInfoHolder holder,
      final int i,
      @NotNull final Runnable action) {
    if (i == visitors.length) {
      action.run();
    } else {
      if (!visitors[i].analyze(
          myFile,
          myUpdateAll,
          holder,
          new Runnable() {
            @Override
            public void run() {
              analyzeByVisitors(progress, visitors, holder, i + 1, action);
            }
          })) {
        cancelAndRestartDaemonLater(progress, myProject, this);
      }
    }
  }

  private static HighlightVisitor[] filterVisitors(
      HighlightVisitor[] highlightVisitors, final PsiFile file) {
    final List<HighlightVisitor> visitors =
        new ArrayList<HighlightVisitor>(highlightVisitors.length);
    List<HighlightVisitor> list = Arrays.asList(highlightVisitors);
    for (HighlightVisitor visitor :
        DumbService.getInstance(file.getProject()).filterByDumbAwareness(list)) {
      if (visitor.suitableForFile(file)) visitors.add(visitor);
    }
    LOG.assertTrue(!visitors.isEmpty(), list);

    HighlightVisitor[] visitorArray = visitors.toArray(new HighlightVisitor[visitors.size()]);
    Arrays.sort(visitorArray, VISITOR_ORDER_COMPARATOR);
    return visitorArray;
  }

  static Void cancelAndRestartDaemonLater(
      ProgressIndicator progress, final Project project, TextEditorHighlightingPass pass) {
    PassExecutorService.log(progress, pass, "Cancel and restart");
    progress.cancel();
    ApplicationManager.getApplication()
        .invokeLater(
            new Runnable() {
              @Override
              public void run() {
                try {
                  Thread.sleep(new Random().nextInt(100));
                } catch (InterruptedException e) {
                  LOG.error(e);
                }
                DaemonCodeAnalyzer.getInstance(project).restart();
              }
            },
            project.getDisposed());
    throw new ProcessCanceledException();
  }

  private boolean forceHighlightParents() {
    boolean forceHighlightParents = false;
    for (HighlightRangeExtension extension :
        Extensions.getExtensions(HighlightRangeExtension.EP_NAME)) {
      if (extension.isForceHighlightParents(myFile)) {
        forceHighlightParents = true;
        break;
      }
    }
    return forceHighlightParents;
  }

  protected HighlightInfoHolder createInfoHolder(final PsiFile file) {
    final HighlightInfoFilter[] filters =
        ApplicationManager.getApplication().getExtensions(HighlightInfoFilter.EXTENSION_POINT_NAME);
    return new HighlightInfoHolder(file, getColorsScheme(), filters);
  }

  private static void highlightTodos(
      @NotNull PsiFile file,
      @NotNull CharSequence text,
      int startOffset,
      int endOffset,
      @NotNull ProgressIndicator progress,
      @NotNull ProperTextRange priorityRange,
      @NotNull Collection<HighlightInfo> result,
      @NotNull Collection<HighlightInfo> outsideResult) {
    PsiSearchHelper helper = PsiSearchHelper.SERVICE.getInstance(file.getProject());
    TodoItem[] todoItems = helper.findTodoItems(file, startOffset, endOffset);
    if (todoItems.length == 0) return;

    for (TodoItem todoItem : todoItems) {
      progress.checkCanceled();
      TextRange range = todoItem.getTextRange();
      String description =
          text.subSequence(range.getStartOffset(), range.getEndOffset()).toString();
      TextAttributes attributes = todoItem.getPattern().getAttributes().getTextAttributes();
      HighlightInfo info =
          HighlightInfo.createHighlightInfo(
              HighlightInfoType.TODO, range, description, description, attributes);
      assert info != null;
      if (priorityRange.containsRange(info.getStartOffset(), info.getEndOffset())) {
        result.add(info);
      } else {
        outsideResult.add(info);
      }
    }
  }

  private void reportErrorsToWolf() {
    if (!myFile.getViewProvider().isPhysical()) return; // e.g. errors in evaluate expression
    Project project = myFile.getProject();
    if (!PsiManager.getInstance(project).isInProject(myFile))
      return; // do not report problems in libraries
    VirtualFile file = myFile.getVirtualFile();
    if (file == null) return;

    List<Problem> problems = convertToProblems(getInfos(), file, myHasErrorElement);
    WolfTheProblemSolver wolf = WolfTheProblemSolver.getInstance(project);

    boolean hasErrors = DaemonCodeAnalyzerImpl.hasErrors(project, getDocument());
    if (!hasErrors || isWholeFileHighlighting()) {
      wolf.reportProblems(file, problems);
    } else {
      wolf.weHaveGotProblems(file, problems);
    }
  }

  @Override
  public double getProgress() {
    // do not show progress of visible highlighters update
    return myUpdateAll ? super.getProgress() : -1;
  }

  private static List<Problem> convertToProblems(
      final Collection<HighlightInfo> infos,
      final VirtualFile file,
      final boolean hasErrorElement) {
    List<Problem> problems = new SmartList<Problem>();
    for (HighlightInfo info : infos) {
      if (info.getSeverity() == HighlightSeverity.ERROR) {
        Problem problem = new ProblemImpl(file, info, hasErrorElement);
        problems.add(problem);
      }
    }
    return problems;
  }

  @Override
  public String toString() {
    return super.toString()
        + " updateAll="
        + myUpdateAll
        + " range=("
        + myStartOffset
        + ","
        + myEndOffset
        + ")";
  }

  public void setFailFastOnAcquireReadAction(boolean failFastOnAcquireReadAction) {
    myFailFastOnAcquireReadAction = failFastOnAcquireReadAction;
  }
}
public class StructureViewComponent extends SimpleToolWindowPanel
    implements TreeActionsOwner, DataProvider, StructureView.Scrollable {
  private static final Logger LOG =
      Logger.getInstance("#com.intellij.ide.structureView.newStructureView.StructureViewComponent");
  @NonNls private static final String ourHelpID = "viewingStructure.fileStructureView";

  private StructureTreeBuilder myAbstractTreeBuilder;

  private FileEditor myFileEditor;
  private final TreeModelWrapper myTreeModelWrapper;

  private StructureViewState myStructureViewState;
  private boolean myAutoscrollFeedback;

  private final Alarm myAutoscrollAlarm = new Alarm();

  private final CopyPasteDelegator myCopyPasteDelegator;
  private final MyAutoScrollToSourceHandler myAutoScrollToSourceHandler;
  private final AutoScrollFromSourceHandler myAutoScrollFromSourceHandler;

  private static final Key<StructureViewState> STRUCTURE_VIEW_STATE_KEY =
      Key.create("STRUCTURE_VIEW_STATE");
  private final Project myProject;
  private final StructureViewModel myTreeModel;
  private static int ourSettingsModificationCount;
  private Tree myTree;

  public StructureViewComponent(
      FileEditor editor, StructureViewModel structureViewModel, Project project) {
    this(editor, structureViewModel, project, true);
  }

  public StructureViewComponent(
      final FileEditor editor,
      final StructureViewModel structureViewModel,
      final Project project,
      final boolean showRootNode) {
    super(true, true);

    myProject = project;
    myFileEditor = editor;
    myTreeModel = structureViewModel;
    myTreeModelWrapper = new TreeModelWrapper(myTreeModel, this);

    SmartTreeStructure treeStructure =
        new SmartTreeStructure(project, myTreeModelWrapper) {
          public void rebuildTree() {
            if (!isDisposed()) {
              super.rebuildTree();
            }
          }

          public boolean isToBuildChildrenInBackground(final Object element) {
            return getRootElement() == element;
          }

          protected TreeElementWrapper createTree() {
            return new StructureViewTreeElementWrapper(myProject, myModel.getRoot(), myModel);
          }

          @Override
          public String toString() {
            return "structure view tree structure(model=" + myTreeModel + ")";
          }
        };

    final DefaultTreeModel model =
        new DefaultTreeModel(new DefaultMutableTreeNode(treeStructure.getRootElement()));
    myTree = new Tree(model);
    myTree.setRootVisible(showRootNode);
    myTree.setShowsRootHandles(true);

    myAbstractTreeBuilder =
        new StructureTreeBuilder(
            project,
            myTree,
            (DefaultTreeModel) myTree.getModel(),
            treeStructure,
            myTreeModelWrapper) {
          @Override
          protected boolean validateNode(Object child) {
            return isValid(child);
          }
        };
    Disposer.register(this, myAbstractTreeBuilder);
    Disposer.register(
        myAbstractTreeBuilder,
        new Disposable() {
          public void dispose() {
            storeState();
          }
        });

    setContent(ScrollPaneFactory.createScrollPane(myAbstractTreeBuilder.getTree()));

    myAbstractTreeBuilder.getTree().setCellRenderer(new NodeRenderer());

    myAutoScrollToSourceHandler = new MyAutoScrollToSourceHandler();
    myAutoScrollFromSourceHandler = new MyAutoScrollFromSourceHandler(myProject, this);

    JComponent toolbarComponent =
        ActionManager.getInstance()
            .createActionToolbar(ActionPlaces.STRUCTURE_VIEW_TOOLBAR, createActionGroup(), true)
            .getComponent();
    setToolbar(toolbarComponent);

    installTree();

    myCopyPasteDelegator =
        new CopyPasteDelegator(myProject, getTree()) {
          @NotNull
          protected PsiElement[] getSelectedElements() {
            return getSelectedPsiElements();
          }
        };
  }

  private void installTree() {
    getTree().getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
    myAutoScrollToSourceHandler.install(getTree());
    myAutoScrollFromSourceHandler.install();

    TreeUtil.installActions(getTree());
    new TreeSpeedSearch(getTree());

    addTreeKeyListener();
    addTreeMouseListeners();
    restoreState();
  }

  private PsiElement[] getSelectedPsiElements() {
    return filterPsiElements(getSelectedElements());
  }

  @NotNull
  private static PsiElement[] filterPsiElements(Object[] selectedElements) {
    if (selectedElements == null) {
      return PsiElement.EMPTY_ARRAY;
    }
    ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>();

    for (Object selectedElement : selectedElements) {
      if (selectedElement instanceof PsiElement) {
        psiElements.add((PsiElement) selectedElement);
      }
    }
    return PsiUtilBase.toPsiElementArray(psiElements);
  }

  private Object[] getSelectedElements() {
    final JTree tree = getTree();
    return tree != null
        ? convertPathsToValues(tree.getSelectionPaths())
        : ArrayUtil.EMPTY_OBJECT_ARRAY;
  }

  @Nullable
  private Object[] getSelectedTreeElements() {
    final JTree tree = getTree();
    return tree != null ? convertPathsToTreeElements(tree.getSelectionPaths()) : null;
  }

  private static Object[] convertPathsToValues(TreePath[] selectionPaths) {
    if (selectionPaths != null) {
      List<Object> result = new ArrayList<Object>();

      for (TreePath selectionPath : selectionPaths) {
        final Object userObject =
            ((DefaultMutableTreeNode) selectionPath.getLastPathComponent()).getUserObject();
        if (userObject instanceof AbstractTreeNode) {
          Object value = ((AbstractTreeNode) userObject).getValue();
          if (value instanceof StructureViewTreeElement) {
            value = ((StructureViewTreeElement) value).getValue();
          }
          result.add(value);
        }
      }
      return ArrayUtil.toObjectArray(result);
    } else {
      return null;
    }
  }

  @Nullable
  private static Object[] convertPathsToTreeElements(TreePath[] selectionPaths) {
    if (selectionPaths != null) {
      Object[] result = new Object[selectionPaths.length];

      for (int i = 0; i < selectionPaths.length; i++) {
        Object userObject =
            ((DefaultMutableTreeNode) selectionPaths[i].getLastPathComponent()).getUserObject();
        if (!(userObject instanceof AbstractTreeNode)) return null;
        result[i] = ((AbstractTreeNode) userObject).getValue();
      }
      return result;
    } else {
      return null;
    }
  }

  private void addTreeMouseListeners() {
    EditSourceOnDoubleClickHandler.install(getTree());
    CustomizationUtil.installPopupHandler(
        getTree(), IdeActions.GROUP_STRUCTURE_VIEW_POPUP, ActionPlaces.STRUCTURE_VIEW_POPUP);
  }

  private void addTreeKeyListener() {
    getTree()
        .addKeyListener(
            new KeyAdapter() {
              public void keyPressed(KeyEvent e) {
                if (KeyEvent.VK_ENTER == e.getKeyCode()) {
                  DataContext dataContext = DataManager.getInstance().getDataContext(getTree());
                  OpenSourceUtil.openSourcesFrom(dataContext, false);
                } else if (KeyEvent.VK_ESCAPE == e.getKeyCode()) {
                  if (e.isConsumed()) {
                    return;
                  }
                  PsiCopyPasteManager copyPasteManager = PsiCopyPasteManager.getInstance();
                  boolean[] isCopied = new boolean[1];
                  if (copyPasteManager.getElements(isCopied) != null && !isCopied[0]) {
                    copyPasteManager.clear();
                    e.consume();
                  }
                }
              }
            });
  }

  public void storeState() {
    if (!isDisposed()) {
      myStructureViewState = getState();
      myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, myStructureViewState);
    }
  }

  public StructureViewState getState() {
    StructureViewState structureViewState = new StructureViewState();
    if (getTree() != null) {
      structureViewState.setExpandedElements(getExpandedElements());
      structureViewState.setSelectedElements(getSelectedElements());
    }
    return structureViewState;
  }

  private Object[] getExpandedElements() {
    final JTree tree = getTree();
    if (tree == null) return ArrayUtil.EMPTY_OBJECT_ARRAY;
    final List<TreePath> expandedPaths = TreeUtil.collectExpandedPaths(tree);
    return convertPathsToValues(expandedPaths.toArray(new TreePath[expandedPaths.size()]));
  }

  public void restoreState() {
    myStructureViewState = myFileEditor.getUserData(STRUCTURE_VIEW_STATE_KEY);
    if (myStructureViewState == null) {
      TreeUtil.expand(getTree(), 2);
    } else {
      expandStoredElements();
      selectStoredElements();
      myFileEditor.putUserData(STRUCTURE_VIEW_STATE_KEY, null);
      myStructureViewState = null;
    }
  }

  private void selectStoredElements() {
    Object[] selectedPsiElements = null;

    if (myStructureViewState != null) {
      selectedPsiElements = myStructureViewState.getSelectedElements();
    }

    if (selectedPsiElements == null) {
      getTree().setSelectionPath(new TreePath(getRootNode().getPath()));
    } else {
      for (Object element : selectedPsiElements) {
        if (element instanceof PsiElement && !((PsiElement) element).isValid()) {
          continue;
        }
        addSelectionPathTo(element);
      }
    }
  }

  public void addSelectionPathTo(final Object element) {
    DefaultMutableTreeNode node = myAbstractTreeBuilder.getNodeForElement(element);
    if (node != null) {
      final JTree tree = getTree();
      final TreePath path = new TreePath(node.getPath());
      if (node == tree.getModel().getRoot() && !tree.isExpanded(path)) tree.expandPath(path);
      tree.addSelectionPath(path);
    }
  }

  private DefaultMutableTreeNode getRootNode() {
    return (DefaultMutableTreeNode) getTree().getModel().getRoot();
  }

  private void expandStoredElements() {
    Object[] expandedPsiElements = null;

    if (myStructureViewState != null) {
      expandedPsiElements = myStructureViewState.getExpandedElements();
    }

    if (expandedPsiElements == null) {
      getTree().expandPath(new TreePath(getRootNode().getPath()));
    } else {
      for (Object element : expandedPsiElements) {
        if (element instanceof PsiElement && !((PsiElement) element).isValid()) {
          continue;
        }
        expandPathToElement(element);
      }
    }
  }

  protected ActionGroup createActionGroup() {
    DefaultActionGroup result = new DefaultActionGroup();
    Sorter[] sorters = myTreeModel.getSorters();
    for (final Sorter sorter : sorters) {
      if (sorter.isVisible()) {
        result.add(new TreeActionWrapper(sorter, this));
      }
    }
    if (sorters.length > 0) {
      result.addSeparator();
    }

    Grouper[] groupers = myTreeModel.getGroupers();
    for (Grouper grouper : groupers) {
      result.add(new TreeActionWrapper(grouper, this));
    }
    Filter[] filters = myTreeModel.getFilters();
    for (Filter filter : filters) {
      result.add(new TreeActionWrapper(filter, this));
    }
    if (myTreeModel instanceof ProvidingTreeModel) {
      final Collection<NodeProvider> providers =
          ((ProvidingTreeModel) myTreeModel).getNodeProviders();
      for (NodeProvider provider : providers) {
        result.add(new TreeActionWrapper(provider, this));
      }
    }

    result.add(new ExpandAllAction(getTree()));
    result.add(new CollapseAllAction(getTree()));
    if (showScrollToFromSourceActions()) {
      result.addSeparator();

      result.add(myAutoScrollToSourceHandler.createToggleAction());
      result.add(myAutoScrollFromSourceHandler.createToggleAction());
    }
    return result;
  }

  protected boolean showScrollToFromSourceActions() {
    return true;
  }

  public FileEditor getFileEditor() {
    return myFileEditor;
  }

  public AsyncResult<AbstractTreeNode> expandPathToElement(Object element) {
    if (myAbstractTreeBuilder == null) return new AsyncResult.Rejected<AbstractTreeNode>();

    ArrayList<AbstractTreeNode> pathToElement = getPathToElement(element);
    if (pathToElement.isEmpty()) return new AsyncResult.Rejected<AbstractTreeNode>();

    final AsyncResult<AbstractTreeNode> result = new AsyncResult<AbstractTreeNode>();
    final AbstractTreeNode toExpand = pathToElement.get(pathToElement.size() - 1);
    myAbstractTreeBuilder.expand(
        toExpand,
        new Runnable() {
          public void run() {
            result.setDone(toExpand);
          }
        });

    return result;
  }

  public boolean select(final Object element, final boolean requestFocus) {
    myAbstractTreeBuilder
        .getReady(this)
        .doWhenDone(
            new Runnable() {
              public void run() {
                expandPathToElement(element)
                    .doWhenDone(
                        new AsyncResult.Handler<AbstractTreeNode>() {
                          public void run(AbstractTreeNode abstractTreeNode) {
                            myAbstractTreeBuilder.select(
                                abstractTreeNode,
                                new Runnable() {
                                  public void run() {
                                    if (requestFocus) {
                                      IdeFocusManager.getInstance(myProject)
                                          .requestFocus(myAbstractTreeBuilder.getTree(), false);
                                    }
                                  }
                                });
                          }
                        });
              }
            });
    return true;
  }

  private ArrayList<AbstractTreeNode> getPathToElement(Object element) {
    ArrayList<AbstractTreeNode> result = new ArrayList<AbstractTreeNode>();
    final AbstractTreeStructure treeStructure = myAbstractTreeBuilder.getTreeStructure();
    if (treeStructure != null) {
      addToPath(
          (AbstractTreeNode) treeStructure.getRootElement(),
          element,
          result,
          new THashSet<Object>());
    }
    return result;
  }

  private static boolean addToPath(
      AbstractTreeNode<?> rootElement,
      Object element,
      ArrayList<AbstractTreeNode> result,
      Collection<Object> processedElements) {
    Object value = rootElement.getValue();
    if (value instanceof StructureViewTreeElement) {
      value = ((StructureViewTreeElement) value).getValue();
    }
    if (!processedElements.add(value)) {
      return false;
    }

    if (Comparing.equal(value, element)) {
      result.add(0, rootElement);
      return true;
    }

    Collection<? extends AbstractTreeNode> children = rootElement.getChildren();
    for (AbstractTreeNode child : children) {
      if (addToPath(child, element, result, processedElements)) {
        result.add(0, rootElement);
        return true;
      }
    }

    return false;
  }

  private static DefaultMutableTreeNode findInChildren(
      DefaultMutableTreeNode currentTreeNode, AbstractTreeNode topPathElement) {
    for (int i = 0; i < currentTreeNode.getChildCount(); i++) {
      TreeNode child = currentTreeNode.getChildAt(i);
      if (((DefaultMutableTreeNode) child).getUserObject().equals(topPathElement)) {
        return (DefaultMutableTreeNode) child;
      }
    }
    return null;
  }

  private void scrollToSelectedElement() {
    if (myAutoscrollFeedback) {
      myAutoscrollFeedback = false;
      return;
    }

    StructureViewFactoryImpl structureViewFactory =
        (StructureViewFactoryImpl) StructureViewFactoryEx.getInstance(myProject);

    if (!structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE) {
      return;
    }

    myAutoscrollAlarm.cancelAllRequests();
    myAutoscrollAlarm.addRequest(
        new Runnable() {
          public void run() {
            if (myAbstractTreeBuilder == null) {
              return;
            }
            try {
              selectViewableElement();
            } catch (IndexNotReadyException ignore) {
            }
          }
        },
        1000);
  }

  private void selectViewableElement() {
    PsiDocumentManager.getInstance(myProject).commitAllDocuments();
    final Object currentEditorElement = myTreeModel.getCurrentEditorElement();
    if (currentEditorElement != null) {
      select(currentEditorElement, false);
    }
  }

  public void dispose() {
    LOG.assertTrue(EventQueue.isDispatchThread(), Thread.currentThread().getName());
    myAbstractTreeBuilder = null;
    // this will also dispose wrapped TreeModel
    myTreeModelWrapper.dispose();
    myFileEditor = null;
  }

  public boolean isDisposed() {
    return myAbstractTreeBuilder == null;
  }

  public void centerSelectedRow() {
    TreePath path = getTree().getSelectionPath();
    if (path == null) {
      return;
    }
    myAutoScrollToSourceHandler.setShouldAutoScroll(false);
    TreeUtil.showRowCentered(getTree(), getTree().getRowForPath(path), false);
    myAutoScrollToSourceHandler.setShouldAutoScroll(true);
  }

  public void setActionActive(String name, boolean state) {
    StructureViewFactoryEx.getInstanceEx(myProject).setActiveAction(name, state);
    rebuild();
    TreeUtil.expand(getTree(), 2);
  }

  protected void rebuild() {
    storeState();
    ++ourSettingsModificationCount;
    ((SmartTreeStructure) myAbstractTreeBuilder.getTreeStructure()).rebuildTree();
    myAbstractTreeBuilder.updateFromRoot();
    restoreState();
  }

  public boolean isActionActive(String name) {
    return !myProject.isDisposed()
        && StructureViewFactoryEx.getInstanceEx(myProject).isActionActive(name);
  }

  public AbstractTreeStructure getTreeStructure() {
    return myAbstractTreeBuilder.getTreeStructure();
  }

  public JTree getTree() {
    return myTree;
  }

  private final class MyAutoScrollToSourceHandler extends AutoScrollToSourceHandler {
    private boolean myShouldAutoScroll = true;

    public void setShouldAutoScroll(boolean shouldAutoScroll) {
      myShouldAutoScroll = shouldAutoScroll;
    }

    protected boolean isAutoScrollMode() {
      return myShouldAutoScroll
          && !myProject.isDisposed()
          && ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject))
              .getState()
              .AUTOSCROLL_MODE;
    }

    protected void setAutoScrollMode(boolean state) {
      ((StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject))
              .getState()
              .AUTOSCROLL_MODE =
          state;
    }

    protected void scrollToSource(Component tree) {
      if (myAbstractTreeBuilder == null) return;
      myAutoscrollFeedback = true;

      Navigatable editSourceDescriptor =
          PlatformDataKeys.NAVIGATABLE.getData(DataManager.getInstance().getDataContext(getTree()));
      if (myFileEditor != null
          && editSourceDescriptor != null
          && editSourceDescriptor.canNavigateToSource()) {
        editSourceDescriptor.navigate(false);
      }
    }
  }

  private class MyAutoScrollFromSourceHandler extends AutoScrollFromSourceHandler {
    private FileEditorPositionListener myFileEditorPositionListener;

    private MyAutoScrollFromSourceHandler(Project project, Disposable parentDisposable) {
      super(project, parentDisposable);
    }

    public void install() {
      addEditorCaretListener();
    }

    public void dispose() {
      myTreeModel.removeEditorPositionListener(myFileEditorPositionListener);
    }

    private void addEditorCaretListener() {
      myFileEditorPositionListener =
          new FileEditorPositionListener() {
            public void onCurrentElementChanged() {
              scrollToSelectedElement();
            }
          };
      myTreeModel.addEditorPositionListener(myFileEditorPositionListener);
    }

    protected boolean isAutoScrollMode() {
      StructureViewFactoryImpl structureViewFactory =
          (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject);
      return structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE;
    }

    protected void setAutoScrollMode(boolean state) {
      StructureViewFactoryImpl structureViewFactory =
          (StructureViewFactoryImpl) StructureViewFactory.getInstance(myProject);
      structureViewFactory.getState().AUTOSCROLL_FROM_SOURCE = state;
      final FileEditor[] selectedEditors =
          FileEditorManager.getInstance(myProject).getSelectedEditors();
      if (selectedEditors.length > 0 && state) {
        scrollToSelectedElement();
      }
    }
  }

  public Object getData(String dataId) {
    if (LangDataKeys.PSI_ELEMENT.is(dataId)) {
      TreePath path = getSelectedUniquePath();
      if (path == null) return null;
      DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
      Object userObject = node.getUserObject();
      if (!(userObject instanceof AbstractTreeNode)) return null;
      AbstractTreeNode descriptor = (AbstractTreeNode) userObject;
      Object element = descriptor.getValue();
      if (element instanceof StructureViewTreeElement) {
        element = ((StructureViewTreeElement) element).getValue();
      }
      if (!(element instanceof PsiElement)) return null;
      if (!((PsiElement) element).isValid()) return null;
      return element;
    }
    if (LangDataKeys.PSI_ELEMENT_ARRAY.is(dataId)) {
      return convertToPsiElementsArray(getSelectedElements());
    }
    if (PlatformDataKeys.FILE_EDITOR.is(dataId)) {
      return myFileEditor;
    }
    if (PlatformDataKeys.CUT_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCutProvider();
    }
    if (PlatformDataKeys.COPY_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getCopyProvider();
    }
    if (PlatformDataKeys.PASTE_PROVIDER.is(dataId)) {
      return myCopyPasteDelegator.getPasteProvider();
    }
    if (PlatformDataKeys.NAVIGATABLE.is(dataId)) {
      Object[] selectedElements = getSelectedTreeElements();
      if (selectedElements == null || selectedElements.length == 0) return null;
      if (selectedElements[0] instanceof Navigatable) {
        return selectedElements[0];
      }
    }
    if (PlatformDataKeys.HELP_ID.is(dataId)) {
      return getHelpID();
    }
    return super.getData(dataId);
  }

  @Nullable
  private static PsiElement[] convertToPsiElementsArray(final Object[] selectedElements) {
    if (selectedElements == null) return null;
    ArrayList<PsiElement> psiElements = new ArrayList<PsiElement>();
    for (Object selectedElement : selectedElements) {
      if (selectedElement instanceof PsiElement && ((PsiElement) selectedElement).isValid()) {
        psiElements.add((PsiElement) selectedElement);
      }
    }
    return PsiUtilBase.toPsiElementArray(psiElements);
  }

  @Nullable
  private TreePath getSelectedUniquePath() {
    JTree tree = getTree();
    if (tree == null) return null;
    TreePath[] paths = tree.getSelectionPaths();
    return paths == null || paths.length != 1 ? null : paths[0];
  }

  public StructureViewModel getTreeModel() {
    return myTreeModel;
  }

  public boolean navigateToSelectedElement(boolean requestFocus) {
    return select(myTreeModel.getCurrentEditorElement(), requestFocus);
  }

  public void doUpdate() {
    assert ApplicationManager.getApplication().isUnitTestMode();
    myAbstractTreeBuilder.addRootToUpdate();
  }

  // todo [kirillk] dirty hack for discovering invalid psi elements, to delegate it to a proper
  // place after 8.1
  public static boolean isValid(Object treeElement) {
    if (treeElement instanceof StructureViewTreeElementWrapper) {
      final StructureViewTreeElementWrapper wrapper = (StructureViewTreeElementWrapper) treeElement;
      if (wrapper.getValue() instanceof PsiTreeElementBase) {
        final PsiTreeElementBase psiNode = (PsiTreeElementBase) wrapper.getValue();
        return psiNode.isValid();
      }
    }
    return true;
  }

  public static class StructureViewTreeElementWrapper extends TreeElementWrapper
      implements NodeDescriptorProvidingKey {
    private long childrenStamp = -1;
    private long modificationCountForChildren = ourSettingsModificationCount;

    public StructureViewTreeElementWrapper(
        Project project, TreeElement value, TreeModel treeModel) {
      super(project, value, treeModel);
    }

    @NotNull
    public Object getKey() {
      StructureViewTreeElement element = (StructureViewTreeElement) getValue();
      if (element instanceof NodeDescriptorProvidingKey)
        return ((NodeDescriptorProvidingKey) element).getKey();
      Object value = element.getValue();
      return value == null ? this : value;
    }

    @NotNull
    public Collection<AbstractTreeNode> getChildren() {
      if (ourSettingsModificationCount != modificationCountForChildren) {
        resetChildren();
        modificationCountForChildren = ourSettingsModificationCount;
      }

      final Object o = unwrapValue(getValue());
      long currentStamp;
      if ((o instanceof PsiElement
              && ((PsiElement) o).getNode() instanceof CompositeElement
              && childrenStamp
                  != (currentStamp =
                      ((CompositeElement) ((PsiElement) o).getNode()).getModificationCount()))
          || (o instanceof ModificationTracker
              && childrenStamp
                  != (currentStamp = ((ModificationTracker) o).getModificationCount()))) {
        resetChildren();
        childrenStamp = currentStamp;
      }
      try {
        return super.getChildren();
      } catch (IndexNotReadyException ignore) {
        return Collections.emptyList();
      }
    }

    @Override
    public boolean isAlwaysShowPlus() {
      if (getElementInfoProvider() != null) {
        return getElementInfoProvider().isAlwaysShowsPlus((StructureViewTreeElement) getValue());
      }
      return true;
    }

    @Override
    public boolean isAlwaysLeaf() {
      if (getElementInfoProvider() != null) {
        return getElementInfoProvider().isAlwaysLeaf((StructureViewTreeElement) getValue());
      }

      return false;
    }

    @Nullable
    private StructureViewModel.ElementInfoProvider getElementInfoProvider() {
      if (myTreeModel instanceof StructureViewModel.ElementInfoProvider) {
        return ((StructureViewModel.ElementInfoProvider) myTreeModel);
      } else if (myTreeModel instanceof TreeModelWrapper) {
        StructureViewModel model = ((TreeModelWrapper) myTreeModel).getModel();
        if (model instanceof StructureViewModel.ElementInfoProvider) {
          return (StructureViewModel.ElementInfoProvider) model;
        }
      }

      return null;
    }

    protected TreeElementWrapper createChildNode(final TreeElement child) {
      return new StructureViewTreeElementWrapper(myProject, child, myTreeModel);
    }

    @Override
    protected GroupWrapper createGroupWrapper(
        final Project project, Group group, final TreeModel treeModel) {
      return new StructureViewGroup(project, group, treeModel);
    }

    public boolean equals(Object o) {
      if (o instanceof StructureViewTreeElementWrapper) {
        return Comparing.equal(
            unwrapValue(getValue()), unwrapValue(((StructureViewTreeElementWrapper) o).getValue()));
      } else if (o instanceof StructureViewTreeElement) {
        return Comparing.equal(unwrapValue(getValue()), ((StructureViewTreeElement) o).getValue());
      }
      return false;
    }

    private static Object unwrapValue(Object o) {
      if (o instanceof StructureViewTreeElement) {
        return ((StructureViewTreeElement) o).getValue();
      } else {
        return o;
      }
    }

    public int hashCode() {
      final Object o = unwrapValue(getValue());

      return o != null ? o.hashCode() : 0;
    }

    private class StructureViewGroup extends GroupWrapper {
      public StructureViewGroup(Project project, Group group, TreeModel treeModel) {
        super(project, group, treeModel);
      }

      @Override
      protected TreeElementWrapper createChildNode(TreeElement child) {
        return new StructureViewTreeElementWrapper(getProject(), child, myTreeModel);
      }

      @Override
      protected GroupWrapper createGroupWrapper(Project project, Group group, TreeModel treeModel) {
        return new StructureViewGroup(project, group, treeModel);
      }

      @Override
      public boolean isAlwaysShowPlus() {
        return true;
      }
    }
  }

  public String getHelpID() {
    return ourHelpID;
  }

  public Dimension getCurrentSize() {
    return myTree.getSize();
  }

  public void setReferenceSizeWhileInitializing(Dimension size) {
    _setRefSize(size);

    if (size != null) {
      myAbstractTreeBuilder
          .getReady(this)
          .doWhenDone(
              new Runnable() {
                public void run() {
                  _setRefSize(null);
                }
              });
    }
  }

  private void _setRefSize(Dimension size) {
    myTree.setPreferredSize(size);
    myTree.setMinimumSize(size);
    myTree.setMaximumSize(size);

    myTree.revalidate();
    myTree.repaint();
  }
}
/** @author Gregory Shrago */
public class QuickEditHandler extends DocumentAdapter implements Disposable {
  private final Project myProject;
  private final QuickEditAction myAction;

  private final Editor myEditor;
  private final Document myOrigDocument;

  private final Document myNewDocument;
  private final PsiFile myNewFile;
  private final LightVirtualFile myNewVirtualFile;

  private final long myOrigCreationStamp;
  private EditorWindow mySplittedWindow;
  private boolean myCommittingToOriginal;

  private final PsiFile myInjectedFile;
  private final List<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>> myMarkers =
      ContainerUtil.newLinkedList();

  @Nullable private final RangeMarker myAltFullRange;
  private static final Key<String> REPLACEMENT_KEY = Key.create("REPLACEMENT_KEY");

  QuickEditHandler(
      Project project,
      @NotNull PsiFile injectedFile,
      final PsiFile origFile,
      Editor editor,
      QuickEditAction action) {
    myProject = project;
    myEditor = editor;
    myAction = action;
    myOrigDocument = editor.getDocument();
    Place shreds = InjectedLanguageUtil.getShreds(injectedFile);
    FileType fileType = injectedFile.getFileType();
    Language language = injectedFile.getLanguage();
    PsiLanguageInjectionHost.Shred firstShred = ContainerUtil.getFirstItem(shreds);

    PsiFileFactory factory = PsiFileFactory.getInstance(project);
    String text = InjectedLanguageManager.getInstance(project).getUnescapedText(injectedFile);
    String newFileName =
        StringUtil.notNullize(language.getDisplayName(), "Injected")
            + " Fragment "
            + "("
            + origFile.getName()
            + ":"
            + firstShred.getHost().getTextRange().getStartOffset()
            + ")"
            + "."
            + fileType.getDefaultExtension();

    // preserve \r\n as it is done in MultiHostRegistrarImpl
    myNewFile = factory.createFileFromText(newFileName, language, text, true, false);
    myNewVirtualFile = ObjectUtils.assertNotNull((LightVirtualFile) myNewFile.getVirtualFile());
    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());

    assert myNewFile != null : "PSI file is null";
    assert myNewFile.getTextLength() == myNewVirtualFile.getContent().length()
        : "PSI / Virtual file text mismatch";

    myNewVirtualFile.setOriginalFile(origFile.getVirtualFile());
    // suppress possible errors as in injected mode
    myNewFile.putUserData(
        InjectedLanguageUtil.FRANKENSTEIN_INJECTION,
        injectedFile.getUserData(InjectedLanguageUtil.FRANKENSTEIN_INJECTION));
    myNewFile.putUserData(FileContextUtil.INJECTED_IN_ELEMENT, shreds.getHostPointer());
    myNewDocument = PsiDocumentManager.getInstance(project).getDocument(myNewFile);
    assert myNewDocument != null;
    EditorActionManager.getInstance()
        .setReadonlyFragmentModificationHandler(myNewDocument, new MyQuietHandler());
    myOrigCreationStamp =
        myOrigDocument.getModificationStamp(); // store creation stamp for UNDO tracking
    myOrigDocument.addDocumentListener(this, this);
    myNewDocument.addDocumentListener(this, this);
    EditorFactory editorFactory = ObjectUtils.assertNotNull(EditorFactory.getInstance());
    // not FileEditorManager listener because of RegExp checker and alike
    editorFactory.addEditorFactoryListener(
        new EditorFactoryAdapter() {
          int useCount;

          @Override
          public void editorCreated(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            useCount++;
          }

          @Override
          public void editorReleased(@NotNull EditorFactoryEvent event) {
            if (event.getEditor().getDocument() != myNewDocument) return;
            if (--useCount > 0) return;
            if (Boolean.TRUE.equals(
                myNewVirtualFile.getUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN))) return;

            Disposer.dispose(QuickEditHandler.this);
          }
        },
        this);

    if ("JAVA".equals(firstShred.getHost().getLanguage().getID())) {
      PsiLanguageInjectionHost.Shred lastShred = ContainerUtil.getLastItem(shreds);
      myAltFullRange =
          myOrigDocument.createRangeMarker(
              firstShred.getHostRangeMarker().getStartOffset(),
              lastShred.getHostRangeMarker().getEndOffset());
      myAltFullRange.setGreedyToLeft(true);
      myAltFullRange.setGreedyToRight(true);

      initGuardedBlocks(shreds);
      myInjectedFile = null;
    } else {
      initMarkers(shreds);
      myAltFullRange = null;
      myInjectedFile = injectedFile;
    }
  }

  public boolean isValid() {
    boolean valid =
        myNewVirtualFile.isValid()
            && (myAltFullRange == null && myInjectedFile.isValid()
                || myAltFullRange != null && myAltFullRange.isValid());
    if (valid) {
      for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> t : myMarkers) {
        if (!t.first.isValid() || !t.second.isValid() || t.third.getElement() == null) {
          valid = false;
          break;
        }
      }
    }
    return valid;
  }

  public void navigate(int injectedOffset) {
    if (myAction.isShowInBalloon()) {
      final JComponent component = myAction.createBalloonComponent(myNewFile);
      if (component != null) {
        final Balloon balloon =
            JBPopupFactory.getInstance()
                .createBalloonBuilder(component)
                .setShadow(true)
                .setAnimationCycle(0)
                .setHideOnClickOutside(true)
                .setHideOnKeyOutside(true)
                .setHideOnAction(false)
                .setFillColor(UIUtil.getControlColor())
                .createBalloon();
        new AnAction() {
          @Override
          public void actionPerformed(AnActionEvent e) {
            balloon.hide();
          }
        }.registerCustomShortcutSet(CommonShortcuts.ESCAPE, component);
        Disposer.register(myNewFile.getProject(), balloon);
        final Balloon.Position position = QuickEditAction.getBalloonPosition(myEditor);
        RelativePoint point = JBPopupFactory.getInstance().guessBestPopupLocation(myEditor);
        if (position == Balloon.Position.above) {
          final Point p = point.getPoint();
          point =
              new RelativePoint(
                  point.getComponent(), new Point(p.x, p.y - myEditor.getLineHeight()));
        }
        balloon.show(point, position);
      }
    } else {
      final FileEditorManagerEx fileEditorManager = FileEditorManagerEx.getInstanceEx(myProject);
      final FileEditor[] editors = fileEditorManager.getEditors(myNewVirtualFile);
      if (editors.length == 0) {
        final EditorWindow curWindow = fileEditorManager.getCurrentWindow();
        mySplittedWindow =
            curWindow.split(SwingConstants.HORIZONTAL, false, myNewVirtualFile, true);
      }
      Editor editor =
          fileEditorManager.openTextEditor(
              new OpenFileDescriptor(myProject, myNewVirtualFile, injectedOffset), true);
      // fold missing values
      if (editor != null) {
        editor.putUserData(QuickEditAction.QUICK_EDIT_HANDLER, this);
        final FoldingModel foldingModel = editor.getFoldingModel();
        foldingModel.runBatchFoldingOperation(
            () -> {
              for (RangeMarker o :
                  ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) {
                String replacement = o.getUserData(REPLACEMENT_KEY);
                if (StringUtil.isEmpty(replacement)) continue;
                FoldRegion region =
                    foldingModel.addFoldRegion(o.getStartOffset(), o.getEndOffset(), replacement);
                if (region != null) region.setExpanded(false);
              }
            });
      }
      SwingUtilities.invokeLater(
          () -> myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE));
    }
  }

  @Override
  public void documentChanged(DocumentEvent e) {
    UndoManager undoManager = UndoManager.getInstance(myProject);
    boolean undoOrRedo = undoManager.isUndoInProgress() || undoManager.isRedoInProgress();
    if (undoOrRedo) {
      // allow undo/redo up until 'creation stamp' back in time
      // and check it after action is completed
      if (e.getDocument() == myOrigDocument) {
        //noinspection SSBasedInspection
        SwingUtilities.invokeLater(
            () -> {
              if (myOrigCreationStamp > myOrigDocument.getModificationStamp()) {
                closeEditor();
              }
            });
      }
    } else if (e.getDocument() == myNewDocument) {
      commitToOriginal(e);
      if (!isValid()) {
        ApplicationManager.getApplication()
            .invokeLater(() -> closeEditor(), myProject.getDisposed());
      }
    } else if (e.getDocument() == myOrigDocument) {
      if (myCommittingToOriginal || myAltFullRange != null && myAltFullRange.isValid()) return;
      ApplicationManager.getApplication().invokeLater(() -> closeEditor(), myProject.getDisposed());
    }
  }

  private void closeEditor() {
    boolean unsplit = false;
    if (mySplittedWindow != null && !mySplittedWindow.isDisposed()) {
      final EditorWithProviderComposite[] editors = mySplittedWindow.getEditors();
      if (editors.length == 1 && Comparing.equal(editors[0].getFile(), myNewVirtualFile)) {
        unsplit = true;
      }
    }
    FileEditorManager.getInstance(myProject).closeFile(myNewVirtualFile);
    if (unsplit) {
      for (EditorWindow editorWindow : mySplittedWindow.findSiblings()) {
        editorWindow.unsplit(true);
      }
    }
  }

  public void initMarkers(Place shreds) {
    SmartPointerManager smartPointerManager = SmartPointerManager.getInstance(myProject);
    int curOffset = -1;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      final RangeMarker rangeMarker =
          myNewDocument.createRangeMarker(
              shred.getRange().getStartOffset() + shred.getPrefix().length(),
              shred.getRange().getEndOffset() - shred.getSuffix().length());
      final TextRange rangeInsideHost = shred.getRangeInsideHost();
      PsiLanguageInjectionHost host = shred.getHost();
      RangeMarker origMarker =
          myOrigDocument.createRangeMarker(
              rangeInsideHost.shiftRight(host.getTextRange().getStartOffset()));
      SmartPsiElementPointer<PsiLanguageInjectionHost> elementPointer =
          smartPointerManager.createSmartPsiElementPointer(host);
      Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> markers =
          Trinity.<RangeMarker, RangeMarker, SmartPsiElementPointer>create(
              origMarker, rangeMarker, elementPointer);
      myMarkers.add(markers);

      origMarker.setGreedyToRight(true);
      rangeMarker.setGreedyToRight(true);
      if (origMarker.getStartOffset() > curOffset) {
        origMarker.setGreedyToLeft(true);
        rangeMarker.setGreedyToLeft(true);
      }
      curOffset = origMarker.getEndOffset();
    }
    initGuardedBlocks(shreds);
  }

  private void initGuardedBlocks(Place shreds) {
    int origOffset = -1;
    int curOffset = 0;
    for (PsiLanguageInjectionHost.Shred shred : shreds) {
      Segment hostRangeMarker = shred.getHostRangeMarker();
      int start = shred.getRange().getStartOffset() + shred.getPrefix().length();
      int end = shred.getRange().getEndOffset() - shred.getSuffix().length();
      if (curOffset < start) {
        RangeMarker guard = myNewDocument.createGuardedBlock(curOffset, start);
        if (curOffset == 0 && shred == shreds.get(0)) guard.setGreedyToLeft(true);
        String padding =
            origOffset < 0
                ? ""
                : myOrigDocument.getText().substring(origOffset, hostRangeMarker.getStartOffset());
        guard.putUserData(REPLACEMENT_KEY, fixQuotes(padding));
      }
      curOffset = end;
      origOffset = hostRangeMarker.getEndOffset();
    }
    if (curOffset < myNewDocument.getTextLength()) {
      RangeMarker guard =
          myNewDocument.createGuardedBlock(curOffset, myNewDocument.getTextLength());
      guard.setGreedyToRight(true);
      guard.putUserData(REPLACEMENT_KEY, "");
    }
  }

  private void commitToOriginal(final DocumentEvent e) {
    VirtualFile origVirtualFile = PsiUtilCore.getVirtualFile(myNewFile.getContext());
    myCommittingToOriginal = true;
    try {
      if (origVirtualFile == null
          || !ReadonlyStatusHandler.getInstance(myProject)
              .ensureFilesWritable(origVirtualFile)
              .hasReadonlyFiles()) {
        PostprocessReformattingAspect.getInstance(myProject)
            .disablePostprocessFormattingInside(
                () -> {
                  if (myAltFullRange != null) {
                    altCommitToOriginal(e);
                    return;
                  }
                  commitToOriginalInner();
                });
        PsiDocumentManager.getInstance(myProject)
            .doPostponedOperationsAndUnblockDocument(myOrigDocument);
      }
    } finally {
      myCommittingToOriginal = false;
    }
  }

  private void commitToOriginalInner() {
    final String text = myNewDocument.getText();
    final Map<
            PsiLanguageInjectionHost,
            Set<Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>>>
        map =
            ContainerUtil.classify(
                myMarkers.iterator(),
                new Convertor<
                    Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer>,
                    PsiLanguageInjectionHost>() {
                  @Override
                  public PsiLanguageInjectionHost convert(
                      final Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> o) {
                    final PsiElement element = o.third.getElement();
                    return (PsiLanguageInjectionHost) element;
                  }
                });
    PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject);
    documentManager.commitDocument(myOrigDocument); // commit here and after each manipulator update
    int localInsideFileCursor = 0;
    for (PsiLanguageInjectionHost host : map.keySet()) {
      if (host == null) continue;
      String hostText = host.getText();
      ProperTextRange insideHost = null;
      StringBuilder sb = new StringBuilder();
      for (Trinity<RangeMarker, RangeMarker, SmartPsiElementPointer> entry : map.get(host)) {
        RangeMarker origMarker = entry.first; // check for validity?
        int hostOffset = host.getTextRange().getStartOffset();
        ProperTextRange localInsideHost =
            new ProperTextRange(
                origMarker.getStartOffset() - hostOffset, origMarker.getEndOffset() - hostOffset);
        RangeMarker rangeMarker = entry.second;
        ProperTextRange localInsideFile =
            new ProperTextRange(
                Math.max(localInsideFileCursor, rangeMarker.getStartOffset()),
                rangeMarker.getEndOffset());
        if (insideHost != null) {
          // append unchanged inter-markers fragment
          sb.append(
              hostText.substring(insideHost.getEndOffset(), localInsideHost.getStartOffset()));
        }
        sb.append(
            localInsideFile.getEndOffset() <= text.length() && !localInsideFile.isEmpty()
                ? localInsideFile.substring(text)
                : "");
        localInsideFileCursor = localInsideFile.getEndOffset();
        insideHost = insideHost == null ? localInsideHost : insideHost.union(localInsideHost);
      }
      assert insideHost != null;
      ElementManipulators.getManipulator(host).handleContentChange(host, insideHost, sb.toString());
      documentManager.commitDocument(myOrigDocument);
    }
  }

  private void altCommitToOriginal(@NotNull DocumentEvent e) {
    final PsiFile origPsiFile =
        PsiDocumentManager.getInstance(myProject).getPsiFile(myOrigDocument);
    String newText = myNewDocument.getText();
    // prepare guarded blocks
    LinkedHashMap<String, String> replacementMap = new LinkedHashMap<String, String>();
    int count = 0;
    for (RangeMarker o : ContainerUtil.reverse(((DocumentEx) myNewDocument).getGuardedBlocks())) {
      String replacement = o.getUserData(REPLACEMENT_KEY);
      String tempText = "REPLACE" + (count++) + Long.toHexString(StringHash.calc(replacement));
      newText =
          newText.substring(0, o.getStartOffset()) + tempText + newText.substring(o.getEndOffset());
      replacementMap.put(tempText, replacement);
    }
    // run preformat processors
    final int hostStartOffset = myAltFullRange.getStartOffset();
    myEditor.getCaretModel().moveToOffset(hostStartOffset);
    for (CopyPastePreProcessor preProcessor :
        Extensions.getExtensions(CopyPastePreProcessor.EP_NAME)) {
      newText = preProcessor.preprocessOnPaste(myProject, origPsiFile, myEditor, newText, null);
    }
    myOrigDocument.replaceString(hostStartOffset, myAltFullRange.getEndOffset(), newText);
    // replace temp strings for guarded blocks
    for (String tempText : replacementMap.keySet()) {
      int idx =
          CharArrayUtil.indexOf(
              myOrigDocument.getCharsSequence(),
              tempText,
              hostStartOffset,
              myAltFullRange.getEndOffset());
      myOrigDocument.replaceString(idx, idx + tempText.length(), replacementMap.get(tempText));
    }
    // JAVA: fix occasional char literal concatenation
    fixDocumentQuotes(myOrigDocument, hostStartOffset - 1);
    fixDocumentQuotes(myOrigDocument, myAltFullRange.getEndOffset());

    // reformat
    PsiDocumentManager.getInstance(myProject).commitDocument(myOrigDocument);
    Runnable task =
        () -> {
          try {
            CodeStyleManager.getInstance(myProject)
                .reformatRange(origPsiFile, hostStartOffset, myAltFullRange.getEndOffset(), true);
          } catch (IncorrectOperationException e1) {
            // LOG.error(e);
          }
        };
    DocumentUtil.executeInBulk(myOrigDocument, true, task);

    PsiElement newInjected =
        InjectedLanguageManager.getInstance(myProject)
            .findInjectedElementAt(origPsiFile, hostStartOffset);
    DocumentWindow documentWindow =
        newInjected == null ? null : InjectedLanguageUtil.getDocumentWindow(newInjected);
    if (documentWindow != null) {
      myEditor.getCaretModel().moveToOffset(documentWindow.injectedToHost(e.getOffset()));
      myEditor.getScrollingModel().scrollToCaret(ScrollType.MAKE_VISIBLE);
    }
  }

  private static String fixQuotes(String padding) {
    if (padding.isEmpty()) return padding;
    if (padding.startsWith("'")) padding = '\"' + padding.substring(1);
    if (padding.endsWith("'")) padding = padding.substring(0, padding.length() - 1) + "\"";
    return padding;
  }

  private static void fixDocumentQuotes(Document doc, int offset) {
    if (doc.getCharsSequence().charAt(offset) == '\'') {
      doc.replaceString(offset, offset + 1, "\"");
    }
  }

  @Override
  public void dispose() {
    // noop
  }

  @TestOnly
  public PsiFile getNewFile() {
    return myNewFile;
  }

  public boolean changesRange(TextRange range) {
    if (myAltFullRange != null) {
      return range.intersects(myAltFullRange.getStartOffset(), myAltFullRange.getEndOffset());
    } else if (!myMarkers.isEmpty()) {
      TextRange hostRange =
          TextRange.create(
              myMarkers.get(0).first.getStartOffset(),
              myMarkers.get(myMarkers.size() - 1).first.getEndOffset());
      return range.intersects(hostRange);
    }
    return false;
  }

  private static class MyQuietHandler implements ReadonlyFragmentModificationHandler {
    @Override
    public void handle(final ReadOnlyFragmentModificationException e) {
      // nothing
    }
  }
}
Example #13
0
  /**
   * Parse the section file and fill in the Dbase.
   *
   * @return Success code
   */
  public void parse() throws Exception {
    JOAParameter tempProperties[] = new JOAParameter[100];
    FileInputStream in = null;
    DataInputStream inData = null;
    short inShort = 0;
    long bytesRead = 0;
    long bytesInFile = mFile.length();
    short[] sarray = new short[1];
    int mTotalStns = 0;

    EPSProgressDialog mProgress = new EPSProgressDialog(new Frame(), mProgressStr, Color.blue);
    mProgress.setVisible(true);

    // Get an epic key database specific to JOA
    EPIC_Key_DB mEpicKeyDB = new EPIC_Key_DB("joa_epic.key");

    // Get an epic key database specific to JOA
    EPIC_Key_DB mOrigEpicKeyDB = new EPIC_Key_DB("epic.key");

    // create a vector for temporary storage of the dbases
    Vector dBases = new Vector(100);

    try {
      in = new FileInputStream(mFile);
      BufferedInputStream bis = new BufferedInputStream(in, 1000000);
      inData = new DataInputStream(bis);

      // read version
      short vers = inData.readShort();
      bytesRead += 2;

      if (vers < 2) {
        FileImportException fiex = new FileImportException();
        String errStr = "Invalid version for a JOA binary file";
        fiex.setErrorType(errStr);
        throw fiex;
      }

      // read number of bytes in file description string
      inShort = inData.readShort();
      bytesRead += 2;

      // read the file description String
      byte buf[] = new byte[inShort];
      inData.read(buf, 0, inShort);
      String fileDescrip = new String(buf);
      bytesRead += inShort;

      // create a new open file object
      JOADataFile of = new JOADataFile(fileDescrip);

      // read the number of sections
      int numSections = inData.readShort();
      bytesRead += 2;

      // read each section
      JOASection sech;
      int ord = 0;
      for (int s = 0; s < numSections; s++) {
        mProgress.setPercentComplete(100.0 * ((double) bytesRead / (double) bytesInFile));

        // read the section header
        inShort = inData.readShort();
        bytesRead += 2;
        byte buf1[] = new byte[inShort];
        inData.read(buf1, 0, inShort);
        String sectionDescrip = new String(buf1);
        bytesRead += inShort;

        // read the ship code
        byte bufsc[] = new byte[2];
        bufsc[0] = inData.readByte();
        bufsc[1] = inData.readByte();
        String shipCode = new String(bufsc);
        bytesRead += 2;

        // read num casts
        int numCasts = inData.readShort();
        bytesRead += 2;

        // read num parameters
        int numVars = inData.readShort();
        bytesRead += 2;

        // quality code
        int qcStd = 1;
        if (vers == 4) {
          qcStd = inData.readShort();
          bytesRead += 2;
        }

        // create a new section
        of.mNumSections++;
        sech = new JOASection(of.mNumSections, sectionDescrip, shipCode, numCasts, numVars);

        if (vers == 2) {
          // read the properties
          byte bufv[] = new byte[4];
          for (int p = 0; p < numVars; p++) {
            // parameter name
            inData.read(bufv, 0, 4);
            String tempVar = new String(bufv);
            bytesRead += 4;

            // units
            inShort = inData.readShort();
            bytesRead += 2;

            String units = null;
            if (inShort > 0) {
              byte buf11[] = new byte[inShort];
              inData.read(buf11, 0, inShort);
              units = new String(buf11);
              bytesRead += inShort;
            }

            // convert varnames to UC
            tempVar.toUpperCase();

            // create new property
            tempProperties[p] = new JOAParameter(tempVar, units);

            // add this property to the section property list
            if (tempProperties[p].mUnits == null) {
              tempProperties[p].mUnits =
                  EPS_Util.paramNameToJOAUnits(false, tempProperties[0].mVarLabel);
            }

            if (tempProperties[p].mUnits == null) {
              tempProperties[p].mUnits = new String("na");
            }
            tempProperties[p].mCastOrObs = EPSConstants.ALL_OBS;

            // read the actual scale
            int actScale = inData.readShort();
            bytesRead += 2;

            if (actScale != 0) tempProperties[p].mActScale = 1.0 / (double) actScale;
            else tempProperties[p].mActScale = 1.0;

            // read the actual origin
            int actOrigin = inData.readShort();
            tempProperties[p].mActOrigin = (double) actOrigin * tempProperties[p].mActScale;

            // read reverse y
            int reverseY = inData.readShort();
            bytesRead += 2;

            if (reverseY == 0) tempProperties[p].mReverseY = false;
            else tempProperties[p].mReverseY = true;

            tempProperties[p].mWasCalculated = false;
          }
        } else {
          // read the properties
          String tempVar = null;
          for (int p = 0; p < numVars; p++) {
            // length of parameter name
            inShort = inData.readShort();
            bytesRead += 2;

            // parameter name
            if (inShort > 0) {
              byte buf13[] = new byte[inShort];
              inData.read(buf13, 0, inShort);
              tempVar = new String(buf13);
              bytesRead += inShort;
            }

            // units
            inShort = inData.readShort();
            bytesRead += 2;

            String units = null;
            if (inShort > 0) {
              byte buf11[] = new byte[inShort];
              inData.read(buf11, 0, inShort);
              units = new String(buf11);
              bytesRead += inShort;
            }

            // convert varnames to UC
            tempVar = tempVar.toUpperCase();

            // create new property
            tempProperties[p] = new JOAParameter(tempVar, units);

            // add this property to the section property list
            if (tempProperties[p].mUnits == null) {
              tempProperties[p].mUnits =
                  EPS_Util.paramNameToJOAUnits(false, tempProperties[0].mVarLabel);
            }

            if (tempProperties[p].mUnits == null) {
              tempProperties[p].mUnits = new String("na");
            }
            tempProperties[p].mCastOrObs = EPSConstants.ALL_OBS;

            // read the actual scale
            int actScale = inData.readShort();
            bytesRead += 2;
            if (actScale != 0) tempProperties[p].mActScale = 1.0 / (double) actScale;
            else tempProperties[p].mActScale = 1.0;

            // read the actual origin
            int actOrigin = inData.readShort();
            tempProperties[p].mActOrigin = (double) actOrigin * tempProperties[p].mActScale;

            // read reverse y
            int reverseY = inData.readShort();
            bytesRead += 2;
            if (reverseY == 0) tempProperties[p].mReverseY = false;
            else tempProperties[p].mReverseY = true;

            tempProperties[p].mWasCalculated = false;
          }
        }

        // read the cast headers
        for (int c = 0; c < numCasts; c++) {
          // read station number
          inShort = inData.readShort();
          bytesRead += 2;
          byte bufx[] = new byte[inShort];
          inData.read(bufx, 0, inShort);
          String stnNum = new String(bufx);
          bytesRead += inShort;

          // read cast number
          int castNum = inData.readShort();
          bytesRead += 2;

          double myLat = 0.0;
          double myLon = 0.0;
          if (vers == 2) {
            // read latitude/lon
            int lat = inData.readInt();
            bytesRead += 4;

            int lon = inData.readInt();
            bytesRead += 4;

            myLat = lat * 0.001;
            myLon = lon * 0.001;
          } else if (vers > 2) {
            // read latitude/lon
            myLat = inData.readDouble();
            bytesRead += 8;

            myLon = inData.readDouble();
            bytesRead += 4;
          }

          // read number of bottles
          int numBottles = inData.readShort();
          bytesRead += 2;

          // read the date
          int year = inData.readInt();
          bytesRead += 4;
          int month = inData.readInt();
          bytesRead += 4;
          int day = inData.readInt();
          bytesRead += 4;
          int hour = inData.readInt();
          bytesRead += 4;
          double min = inData.readDouble();
          bytesRead += 8;

          // read bottom
          int bottomdbar = inData.readInt();

          // read station quality
          int stnQual = inData.readShort();
          bytesRead += 2;

          ord++;
          JOAStation sh =
              new JOAStation(
                  ord,
                  shipCode,
                  stnNum,
                  castNum,
                  myLat,
                  myLon,
                  numBottles,
                  year,
                  month,
                  day,
                  hour,
                  min,
                  bottomdbar,
                  stnQual);
          sech.mStations.addElement(sh);
          sh.setType("JOA BOTTLE");

          // make a DBase object
          Dbase db = new Dbase();

          // add the global attributes
          db.addEPSAttribute(
              "CRUISE", EPCHAR, sech.mSectionDescription.length(), sech.mSectionDescription);
          db.addEPSAttribute("CAST", EPCHAR, sh.mStnNum.length(), sh.mStnNum);
          sarray[0] = (short) sh.mBottomDepthInDBARS;
          db.addEPSAttribute("WATER_DEPTH", EPSHORT, 1, sarray);
          db.addEPSAttribute("DATA_ORIGIN", EPCHAR, sech.mShipCode.length(), sech.mShipCode);
          String dType = sh.getType();
          if (dType == null) dType = "UNKN";
          db.addEPSAttribute("DATA_TYPE", EPCHAR, dType.length(), dType);
          sarray[0] = (short) stnQual;
          db.addEPSAttribute("STN_QUALITY", EPSHORT, 1, sarray);
          db.setDataType("JOA BOTTLE");

          // add to temporary collection
          dBases.addElement(db);
        }

        int start = mTotalStns;
        int end = sech.mStations.size() + mTotalStns;
        for (int sc = start; sc < end; sc++) {
          mProgress.setPercentComplete(100.0 * ((double) bytesRead / (double) bytesInFile));

          // get a dBase
          Dbase db = (Dbase) dBases.elementAt(sc);

          // get a station
          JOAStation sh = (JOAStation) sech.mStations.elementAt(sc - start);

          // create an array of arrays to store the data
          double[][] va = new double[sech.mNumVars][sh.mNumBottles];
          int[][] qc = new int[sech.mNumVars][sh.mNumBottles];
          short[] bqc = new short[sh.mNumBottles];
          int presPos = 0;

          // read the bottles
          for (int b = 0; b < sh.mNumBottles; b++) {
            // read the bottle quality code
            bqc[b] = inData.readShort();
            bytesRead += 2;

            for (int v = 0; v < sech.mNumVars; v++) {
              // store the position of the PRES variable
              if (tempProperties[v].mVarLabel.equals("PRES")) presPos = v;

              // get the measured parameter
              double dVarVal = EPSConstants.MISSINGVALUE;
              try {
                dVarVal = inData.readDouble();
              } catch (IOException e) {
                FileImportException fiex = new FileImportException();
                String errStr =
                    "Error reading the parameter data. "
                        + "\n"
                        + "Bottle #"
                        + b
                        + " Parameter #"
                        + v;
                fiex.setErrorType(errStr);
                throw fiex;
              }
              bytesRead += 8;

              // store the value in the multidimensional array
              va[v][b] = dVarVal;

              // get the quality flag
              short flag = (short) EPSConstants.MISSINGVALUE;

              try {
                flag = inData.readShort();
              } catch (IOException e) {
                FileImportException fiex = new FileImportException();
                String errStr =
                    "Error reading the parameter quality code. "
                        + "\n"
                        + "Bottle #"
                        + b
                        + " Parameter #"
                        + v;
                fiex.setErrorType(errStr);
                throw fiex;
              }
              bytesRead += 2;
              qc[v][b] = flag;
            } // for v
          } // for b

          // add the bottle quality codes as an attribute
          db.addEPSAttribute("BOTTLE_QUALITY_CODES", EPSHORT, sh.mNumBottles, bqc);

          // create the axes time = 0, depth = 1, lat = 2, lon = 3
          Axis timeAxis = new Axis();
          Axis zAxis = new Axis();
          Axis latAxis = new Axis();
          Axis lonAxis = new Axis();

          // time axis
          timeAxis.setName("time");
          timeAxis.setTime(true);
          timeAxis.setUnlimited(false);
          timeAxis.setAxisType(EPTAXIS);
          timeAxis.setLen(1);

          int hour = 0;
          if (sh.mHour != EPSConstants.MISSINGVALUE) hour = sh.mHour;

          double mins = 0;
          if (sh.mMinute != EPSConstants.MISSINGVALUE) mins = sh.mMinute;

          // make the time axis units
          String date = "days since ";
          int min = (int) mins;
          double fmin = mins - min;
          int secs = (int) (fmin * 60.0);
          double fsec = (fmin * 60.0) - secs;
          int msec = (int) (fsec * 1000.0);
          String fs = String.valueOf(fsec);
          fs = fs.substring(fs.indexOf(".") + 1, fs.length()).trim();
          int f = 0;
          if (fs != null && fs.length() > 0) f = Integer.valueOf(fs).intValue();

          // sprintf(time_string,"%04d-%02d-%02d %02d:%02d:%02d.%03d",yr,mon,day,hr,min,sec,f);
          String frmt =
              new String(
                  "{0,number,####}-{1,number,00}-{2,number,00} {3,number,00}:{4,number,00}:{5,number,00}.{6,number,000}");
          MessageFormat msgf = new MessageFormat(frmt);

          Object[] objs = {
            new Integer(sh.mYear),
            new Integer(sh.mMonth),
            new Integer(sh.mDay),
            new Integer(hour),
            new Integer(min),
            new Integer(secs),
            new Integer(f)
          };
          StringBuffer out = new StringBuffer();
          msgf.format(objs, out, null);
          String time_string = new String(out);
          date = date + time_string;
          timeAxis.addAttribute(0, "units", EPCHAR, date.length(), date);
          timeAxis.setUnits(date);
          GeoDate gd = null;
          try {
            gd = new GeoDate(sh.mMonth, sh.mDay, sh.mYear, hour, min, secs, msec);
            GeoDate[] ta = {gd};
            MultiArray tma = new ArrayMultiArray(ta);

            timeAxis.setData(tma);
            db.setAxis(timeAxis);
          } catch (Exception ex) {
            GeoDate[] ta = {new GeoDate()};
            MultiArray tma = new ArrayMultiArray(ta);

            timeAxis.setData(tma);
            db.setAxis(timeAxis);
          }

          // add the time axes variable
          EPSVariable var = new EPSVariable();
          var.setOname("time");
          var.setDtype(EPDOUBLE);
          var.setVclass(Double.TYPE);
          var.addAttribute(0, "units", EPCHAR, date.length(), date);
          var.setUnits(date);
          double[] vta = {0.0};
          MultiArray vtma = new ArrayMultiArray(vta);
          try {
            var.setData(vtma);
          } catch (Exception ex) {
          }
          db.addEPSVariable(var);

          // z axis
          zAxis.setName("depth");
          zAxis.setTime(false);
          zAxis.setUnlimited(false);
          zAxis.setLen(sh.mNumBottles);
          zAxis.setAxisType(EPZAXIS);
          zAxis.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.1");
          zAxis.addAttribute(1, "units", EPCHAR, 4, "dbar");
          zAxis.setUnits("dbar");
          zAxis.setFrmt("f10.1");
          // zAxis.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 1;
          zAxis.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          double[] za = new double[sh.mNumBottles];
          for (int b = 0; b < sh.mNumBottles; b++) {
            za[b] = va[presPos][b];
          }
          MultiArray zma = new ArrayMultiArray(za);
          zAxis.setData(zma);
          db.setAxis(zAxis);

          // add the z axes variables
          var = new EPSVariable();
          var.setOname("depth");
          var.setDtype(EPDOUBLE);
          var.setVclass(Double.TYPE);
          var.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.1");
          var.addAttribute(1, "units", EPCHAR, 4, "dbar");
          var.setUnits("dbar");
          var.setFrmt("f10.1");
          // var.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 1;
          var.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          MultiArray zvma = new ArrayMultiArray(za);
          try {
            var.setData(zvma);
          } catch (Exception ex) {
          }
          db.addEPSVariable(var);

          // lat axis
          latAxis.setName("latitude");
          latAxis.setTime(false);
          latAxis.setUnlimited(false);
          latAxis.setLen(1);
          latAxis.setAxisType(EPYAXIS);
          latAxis.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.4");
          latAxis.addAttribute(1, "units", EPCHAR, 7, "degrees");
          latAxis.setUnits("degrees");
          latAxis.setFrmt("f10.4");
          // latAxis.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 500;
          latAxis.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          double lat = sh.mLat;
          double[] la = {lat};
          MultiArray lma = new ArrayMultiArray(la);
          latAxis.setData(lma);
          db.setAxis(latAxis);

          // add the y axes variable
          var = new EPSVariable();
          var.setOname("latitude");
          var.setDtype(EPDOUBLE);
          var.setVclass(Double.TYPE);
          var.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.4");
          var.addAttribute(1, "units", EPCHAR, 7, "degrees");
          var.setUnits("degrees");
          var.setFrmt("f10.4");
          // var.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 500;
          var.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          MultiArray yvma = new ArrayMultiArray(la);
          try {
            var.setData(yvma);
          } catch (Exception ex) {
          }
          db.addEPSVariable(var);

          // lon axis
          lonAxis.setName("longitude");
          lonAxis.setTime(false);
          lonAxis.setUnlimited(false);
          lonAxis.setLen(1);
          lonAxis.setAxisType(EPXAXIS);
          lonAxis.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.4");
          lonAxis.addAttribute(1, "units", EPCHAR, 7, "degrees");
          lonAxis.setUnits("degrees");
          lonAxis.setFrmt("f10.4");
          // lonAxis.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 502;
          lonAxis.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          double lon = sh.mLon;
          double[] lla = {lon};
          lma = new ArrayMultiArray(lla);
          lonAxis.setData(lma);
          db.setAxis(lonAxis);

          // add the x axes variable
          var = new EPSVariable();
          var.setOname("longitude");
          var.setDtype(EPDOUBLE);
          var.setVclass(Double.TYPE);
          var.addAttribute(0, "FORTRAN_format", EPCHAR, 5, "f10.4");
          var.addAttribute(1, "units", EPCHAR, 7, "degrees");
          var.setUnits("degrees");
          var.setFrmt("f10.4");
          // var.addAttribute(2, "type", EPCHAR, 0, "");
          sarray[0] = 502;
          var.addAttribute(2, "epic_code", EPSHORT, 1, sarray);
          MultiArray xvma = new ArrayMultiArray(lla);
          try {
            var.setData(xvma);
          } catch (Exception ex) {
          }

          db.addEPSVariable(var);

          // add the measured variables
          for (int v = 0; v < sech.mNumVars; v++) {
            // if (presPos == v)
            //	continue;

            // create an array of measured EPSVariables for this database
            EPSVariable epsVar = new EPSVariable();

            // initialize the new EPSVariables
            // look this variable up in JOA EPIC_Key. find matching entry in original EPIC Key
            String oname = tempProperties[v].mVarLabel;
            String sname = null;
            String lname = null;
            String gname = null;
            String units = null;
            String ffrmt = null;
            int keyID = -99;
            int type = -99;
            try {
              keyID = mEpicKeyDB.findKey(tempProperties[v].mVarLabel);
              Key key = mOrigEpicKeyDB.findKey(keyID);
              gname = key.getGname();
              sname = key.getSname();
              lname = key.getLname();
              units = key.getUnits();
              ffrmt = key.getFrmt();
              type = key.getType();
            } catch (Exception e) {
              lname = tempProperties[v].mVarLabel;
              gname = tempProperties[v].mVarLabel;
              sname = tempProperties[v].mVarLabel;
              units = tempProperties[v].mUnits;
            }

            // make a new variable
            epsVar = new EPSVariable();

            epsVar.setOname(oname);
            epsVar.setSname(sname);
            epsVar.setLname(lname);
            epsVar.setGname(gname);
            epsVar.setDtype(EPDOUBLE);
            epsVar.setVclass(Double.TYPE);
            int numAttributes = 0;
            if (ffrmt != null) {
              epsVar.addAttribute(numAttributes++, "FORTRAN_format", EPCHAR, ffrmt.length(), ffrmt);
              epsVar.setFrmt(ffrmt);
            }
            if (units != null && units.length() > 0) {
              epsVar.addAttribute(numAttributes++, "units", EPCHAR, units.length(), units);
              epsVar.setUnits(units);
            }
            if (keyID >= 0) {
              sarray[0] = (short) type;
              // epsVar.addAttribute(numAttributes++, "type", EPSHORT, 1, sarray);
            }
            if (keyID >= 0) {
              sarray[0] = (short) keyID;
              epsVar.addAttribute(numAttributes++, "epic_code", EPSHORT, 1, sarray);
            }

            // add the quality code attribute
            String qcVar = oname + "_QC";
            epsVar.addAttribute(numAttributes++, "OBS_QC_VARIABLE", EPCHAR, qcVar.length(), qcVar);

            // connect variable to axis
            epsVar.setDimorder(0, 0);
            epsVar.setDimorder(1, 1);
            epsVar.setDimorder(2, 2);
            epsVar.setDimorder(3, 3);
            epsVar.setT(timeAxis);
            epsVar.setZ(zAxis);
            epsVar.setY(latAxis);
            epsVar.setX(lonAxis);

            // set the data
            // create storage for the measured variables
            double[][][][] vaa = new double[1][sh.mNumBottles][1][1];
            for (int b = 0; b < sh.mNumBottles; b++) {
              vaa[0][b][0][0] = va[v][b];
            }
            MultiArray mdma = new ArrayMultiArray(vaa);
            try {
              epsVar.setData(mdma);
            } catch (Exception ex) {
              System.out.println("throwing");
            }

            // add the variable to the database
            db.addEPSVariable(epsVar);

            // create the quality code variable
            epsVar = new EPSVariable();
            epsVar.setOname(oname + "_QC");
            epsVar.setSname(sname + "_QC");
            epsVar.setLname(sname + "_QC");
            epsVar.setGname(sname + "_QC");
            epsVar.setDtype(EPSHORT);
            epsVar.setVclass(Short.TYPE);

            // connect variable to axis
            epsVar.setDimorder(0, 0);
            epsVar.setDimorder(1, 1);
            epsVar.setDimorder(2, 2);
            epsVar.setDimorder(3, 3);
            epsVar.setT(timeAxis);
            epsVar.setZ(zAxis);
            epsVar.setY(latAxis);
            epsVar.setX(lonAxis);

            // set the data
            // create storage for the qc variables
            short[][][][] qcaa = new short[1][sh.mNumBottles][1][1];
            for (int b = 0; b < sh.mNumBottles; b++) {
              qcaa[0][b][0][0] = (short) qc[v][b];
            }
            MultiArray qcma = new ArrayMultiArray(qcaa);
            try {
              epsVar.setData(qcma);
            } catch (Exception ex) {
              System.out.println("throwing");
            }

            // add the qc variable to the database
            db.addEPSVariable(epsVar);
          } // for v
        }
        mTotalStns += numCasts;
      } // for sc

      // read the file comments
      StringBuffer sb = null;
      while (true) {
        try {
          // read continuation line
          inShort = inData.readShort();
          bytesRead += 2;

          if (inShort == 1) {
            // read number of bytes in file description string
            inShort = inData.readShort();
            bytesRead += 2;

            // read the file description String
            byte buf2[] = new byte[inShort];
            inData.read(buf2, 0, inShort);
            String commentLine = new String(buf2);
            bytesRead += inShort;

            if (sb == null) sb = new StringBuffer(commentLine);
            else sb.append(commentLine);
          }
        } catch (IOException e) {
          break;
        }
      } // while true

      if (sb != null) {
        // make attributes for the comments
        mOwnerDBase.setDataComment(new String(sb));
      }

      // make a sub database in the dbase
      mOwnerDBase.createSubEntries(mTotalStns, mFile.getName());
      for (int d = 0; d < mTotalStns; d++) {
        Dbase db = (Dbase) dBases.elementAt(d);
        mOwnerDBase.addSubEntry(db);
      }
      mProgress.setPercentComplete(100.0);
      mProgress.setVisible(false);
      mProgress.dispose();
      in.close();
    } catch (IOException e) {
      mProgress.setVisible(false);
      mProgress.dispose();
      throw e;
    }
  }
Example #14
0
 public boolean checkSelectRank() {
   for (Key k : keys()) {
     if (k.compareTo(select(rank(k))) != 0) return false;
   }
   return true;
 }
/** @author gregsh */
public abstract class EditorTextFieldCellRenderer implements TableCellRenderer, Disposable {

  private static final Key<SimpleRendererComponent> MY_PANEL_PROPERTY =
      Key.create("EditorTextFieldCellRenderer.MyEditorPanel");

  private final Project myProject;
  private final FileType myFileType;
  private final boolean myInheritFontFromLaF;

  protected EditorTextFieldCellRenderer(
      @Nullable Project project, @Nullable FileType fileType, @NotNull Disposable parent) {
    this(project, fileType, true, parent);
  }

  protected EditorTextFieldCellRenderer(
      @Nullable Project project,
      @Nullable FileType fileType,
      boolean inheritFontFromLaF,
      @NotNull Disposable parent) {
    myProject = project;
    myFileType = fileType;
    myInheritFontFromLaF = inheritFontFromLaF;
    Disposer.register(parent, this);
  }

  protected abstract String getText(JTable table, Object value, int row, int column);

  @Nullable
  protected TextAttributes getTextAttributes(JTable table, Object value, int row, int column) {
    return null;
  }

  @NotNull
  protected EditorColorsScheme getColorScheme(final JTable table) {
    return getEditorPanel(table).getEditor().getColorsScheme();
  }

  protected void customizeEditor(
      @NotNull EditorEx editor, JTable table, Object value, boolean selected, int row, int column) {
    String text = getText(table, value, row, column);
    getEditorPanel(table).setText(text, getTextAttributes(table, value, row, column), selected);
  }

  @Override
  public Component getTableCellRendererComponent(
      JTable table, Object value, boolean selected, boolean focused, int row, int column) {
    RendererComponent panel = getEditorPanel(table);
    EditorEx editor = panel.getEditor();
    editor.getColorsScheme().setEditorFontSize(table.getFont().getSize());

    editor
        .getColorsScheme()
        .setColor(EditorColors.SELECTION_BACKGROUND_COLOR, table.getSelectionBackground());
    editor
        .getColorsScheme()
        .setColor(EditorColors.SELECTION_FOREGROUND_COLOR, table.getSelectionForeground());
    editor.setBackgroundColor(selected ? table.getSelectionBackground() : table.getBackground());
    panel.setSelected(!Comparing.equal(editor.getBackgroundColor(), table.getBackground()));

    panel.setBorder(
        null); // prevents double border painting when ExtendedItemRendererComponentWrapper is used

    customizeEditor(editor, table, value, selected, row, column);
    return panel;
  }

  @NotNull
  private RendererComponent getEditorPanel(final JTable table) {
    RendererComponent panel = UIUtil.getClientProperty(table, MY_PANEL_PROPERTY);
    if (panel != null) {
      DelegateColorScheme scheme = (DelegateColorScheme) panel.getEditor().getColorsScheme();
      scheme.setDelegate(EditorColorsManager.getInstance().getGlobalScheme());
      return panel;
    }

    panel = createRendererComponent(myProject, myFileType, myInheritFontFromLaF);
    Disposer.register(this, panel);
    Disposer.register(
        this,
        new Disposable() {
          @Override
          public void dispose() {
            UIUtil.putClientProperty(table, MY_PANEL_PROPERTY, null);
          }
        });

    table.putClientProperty(MY_PANEL_PROPERTY, panel);
    return panel;
  }

  @NotNull
  protected RendererComponent createRendererComponent(
      @Nullable Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
    return new AbbreviatingRendererComponent(project, fileType, inheritFontFromLaF);
  }

  @Override
  public void dispose() {}

  public abstract static class RendererComponent extends CellRendererPanel implements Disposable {
    private final EditorEx myEditor;
    private final EditorTextField myTextField;
    protected TextAttributes myTextAttributes;
    private boolean mySelected;

    public RendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      Pair<EditorTextField, EditorEx> pair = createEditor(project, fileType, inheritFontFromLaF);
      myTextField = pair.first;
      myEditor = pair.second;
      add(myEditor.getContentComponent());
    }

    public EditorEx getEditor() {
      return myEditor;
    }

    @NotNull
    private static Pair<EditorTextField, EditorEx> createEditor(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      EditorTextField field =
          new EditorTextField(new MyDocument(), project, fileType, false, false);
      field.setSupplementary(true);
      field.setFontInheritedFromLAF(inheritFontFromLaF);
      field.addNotify(); // creates editor

      EditorEx editor = (EditorEx) ObjectUtils.assertNotNull(field.getEditor());
      editor.setRendererMode(true);

      editor.setColorsScheme(editor.createBoundColorSchemeDelegate(null));
      editor.getSettings().setCaretRowShown(false);

      editor.getScrollPane().setBorder(null);

      return Pair.create(field, editor);
    }

    public void setText(String text, @Nullable TextAttributes textAttributes, boolean selected) {
      myTextAttributes = textAttributes;
      mySelected = selected;
      setText(text);
    }

    public abstract void setText(String text);

    @Override
    public void setBackground(Color bg) {
      // allows for striped tables
      if (myEditor != null) {
        myEditor.setBackgroundColor(bg);
      }
      super.setBackground(bg);
    }

    @Override
    public void dispose() {
      remove(myEditor.getContentComponent());
      myTextField.removeNotify();
    }

    protected void setTextToEditor(String text) {
      myEditor.getMarkupModel().removeAllHighlighters();
      myEditor.getDocument().setText(text);
      ((EditorImpl) myEditor).resetSizes();
      myEditor.getHighlighter().setText(text);
      if (myTextAttributes != null) {
        myEditor
            .getMarkupModel()
            .addRangeHighlighter(
                0,
                myEditor.getDocument().getTextLength(),
                HighlighterLayer.ADDITIONAL_SYNTAX,
                myTextAttributes,
                HighlighterTargetArea.EXACT_RANGE);
      }

      ((EditorImpl) myEditor).setPaintSelection(mySelected);
      SelectionModel selectionModel = myEditor.getSelectionModel();
      selectionModel.setSelection(0, mySelected ? myEditor.getDocument().getTextLength() : 0);
    }
  }

  public static class SimpleRendererComponent extends RendererComponent implements Disposable {
    public SimpleRendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      super(project, fileType, inheritFontFromLaF);
    }

    public void setText(String text) {
      setTextToEditor(text);
    }
  }

  public static class AbbreviatingRendererComponent extends RendererComponent {
    private static final char ABBREVIATION_SUFFIX = '\u2026'; // 2026 '...'
    private static final char RETURN_SYMBOL = '\u23ce';

    private final StringBuilder myDocumentTextBuilder = new StringBuilder();

    private Dimension myPreferredSize;
    private String myRawText;

    public AbbreviatingRendererComponent(
        Project project, @Nullable FileType fileType, boolean inheritFontFromLaF) {
      super(project, fileType, inheritFontFromLaF);
    }

    @Override
    public void setText(String text) {
      myRawText = text;
      myPreferredSize = null;
    }

    @Override
    public Dimension getPreferredSize() {
      if (myPreferredSize == null) {
        int maxLineLength = 0;
        int linesCount = 0;

        for (LineTokenizer lt = new LineTokenizer(myRawText); !lt.atEnd(); lt.advance()) {
          maxLineLength = Math.max(maxLineLength, lt.getLength());
          linesCount++;
        }

        FontMetrics fontMetrics =
            ((EditorImpl) getEditor())
                .getFontMetrics(
                    myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN);
        int preferredHeight = getEditor().getLineHeight() * Math.max(1, linesCount);
        int preferredWidth = fontMetrics.charWidth('m') * maxLineLength;

        Insets insets = getInsets();
        if (insets != null) {
          preferredHeight += insets.top + insets.bottom;
          preferredWidth += insets.left + insets.right;
        }

        myPreferredSize = new Dimension(preferredWidth, preferredHeight);
      }
      return myPreferredSize;
    }

    @Override
    protected void paintChildren(Graphics g) {
      updateText(g.getClipBounds());
      super.paintChildren(g);
    }

    private void updateText(Rectangle clip) {
      FontMetrics fontMetrics =
          ((EditorImpl) getEditor())
              .getFontMetrics(
                  myTextAttributes != null ? myTextAttributes.getFontType() : Font.PLAIN);
      Insets insets = getInsets();
      int maxLineWidth = getWidth() - (insets != null ? insets.left + insets.right : 0);

      myDocumentTextBuilder.setLength(0);

      boolean singleLineMode = getHeight() / (float) getEditor().getLineHeight() < 1.1f;
      if (singleLineMode) {
        appendAbbreviated(
            myDocumentTextBuilder,
            myRawText,
            0,
            myRawText.length(),
            fontMetrics,
            maxLineWidth,
            true);
      } else {
        int lineHeight = getEditor().getLineHeight();
        int firstVisibleLine = clip.y / lineHeight;
        float visibleLinesCountFractional = clip.height / (float) lineHeight;
        int linesToAppend = 1 + (int) visibleLinesCountFractional;

        LineTokenizer lt = new LineTokenizer(myRawText);
        for (int line = 0; !lt.atEnd() && line < firstVisibleLine; lt.advance(), line++) {
          myDocumentTextBuilder.append('\n');
        }

        for (int line = 0; !lt.atEnd() && line < linesToAppend; lt.advance(), line++) {
          int start = lt.getOffset();
          int end = start + lt.getLength();
          appendAbbreviated(
              myDocumentTextBuilder, myRawText, start, end, fontMetrics, maxLineWidth, false);
          if (lt.getLineSeparatorLength() > 0) {
            myDocumentTextBuilder.append('\n');
          }
        }
      }

      setTextToEditor(myDocumentTextBuilder.toString());
    }

    private static void appendAbbreviated(
        StringBuilder to,
        String text,
        int start,
        int end,
        FontMetrics metrics,
        int maxWidth,
        boolean replaceLineTerminators) {
      int abbreviationLength =
          abbreviationLength(text, start, end, metrics, maxWidth, replaceLineTerminators);

      if (!replaceLineTerminators) {
        to.append(text, start, start + abbreviationLength);
      } else {
        CharSequenceSubSequence subSeq =
            new CharSequenceSubSequence(text, start, start + abbreviationLength);
        for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) {
          to.append(subSeq, lt.getOffset(), lt.getOffset() + lt.getLength());
          if (lt.getLineSeparatorLength() > 0) {
            to.append(RETURN_SYMBOL);
          }
        }
      }

      if (abbreviationLength != end - start) {
        to.append(ABBREVIATION_SUFFIX);
      }
    }

    private static int abbreviationLength(
        String text,
        int start,
        int end,
        FontMetrics metrics,
        int maxWidth,
        boolean replaceSeparators) {
      if (metrics.charWidth('m') * (end - start) <= maxWidth) return end - start;

      int abbrWidth = metrics.charWidth(ABBREVIATION_SUFFIX);
      int abbrLength = 0;

      CharSequenceSubSequence subSeq = new CharSequenceSubSequence(text, start, end);
      for (LineTokenizer lt = new LineTokenizer(subSeq); !lt.atEnd(); lt.advance()) {
        for (int i = 0; i < lt.getLength(); i++, abbrLength++) {
          abbrWidth += metrics.charWidth(subSeq.charAt(lt.getOffset() + i));
          if (abbrWidth >= maxWidth) return abbrLength;
        }
        if (replaceSeparators && lt.getLineSeparatorLength() != 0) {
          abbrWidth += metrics.charWidth(RETURN_SYMBOL);
          if (abbrWidth >= maxWidth) return abbrLength;
          abbrLength += lt.getLineSeparatorLength();
        }
      }

      return abbrLength;
    }
  }

  private static class MyDocument extends UserDataHolderBase implements DocumentEx {

    RangeMarkerTree<RangeMarkerEx> myRangeMarkers = new RangeMarkerTree<RangeMarkerEx>(this) {};
    char[] myChars = ArrayUtil.EMPTY_CHAR_ARRAY;
    String myString = "";
    LineSet myLineSet = LineSet.createLineSet(myString);

    @Override
    public void setText(@NotNull CharSequence text) {
      String s = StringUtil.convertLineSeparators(text.toString());
      myChars = new char[s.length()];
      s.getChars(0, s.length(), myChars, 0);
      myString = new String(myChars);
      myLineSet = LineSet.createLineSet(myString);
    }

    @Override
    public void setStripTrailingSpacesEnabled(boolean isEnabled) {}

    @NotNull
    @Override
    public LineIterator createLineIterator() {
      return myLineSet.createIterator();
    }

    @Override
    public void setModificationStamp(long modificationStamp) {}

    @Override
    public void addEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {}

    @Override
    public void removeEditReadOnlyListener(@NotNull EditReadOnlyListener listener) {}

    @Override
    public void replaceText(@NotNull CharSequence chars, long newModificationStamp) {}

    @Override
    public void moveText(int srcStart, int srcEnd, int dstOffset) {}

    @Override
    public void suppressGuardedExceptions() {}

    @Override
    public void unSuppressGuardedExceptions() {}

    @Override
    public boolean isInEventsHandling() {
      return false;
    }

    @Override
    public void clearLineModificationFlags() {}

    @Override
    public boolean removeRangeMarker(@NotNull RangeMarkerEx rangeMarker) {
      return myRangeMarkers.removeInterval(rangeMarker);
    }

    @Override
    public void registerRangeMarker(
        @NotNull RangeMarkerEx rangeMarker,
        int start,
        int end,
        boolean greedyToLeft,
        boolean greedyToRight,
        int layer) {
      myRangeMarkers.addInterval(rangeMarker, start, end, greedyToLeft, greedyToRight, layer);
    }

    @Override
    public boolean isInBulkUpdate() {
      return false;
    }

    @Override
    public void setInBulkUpdate(boolean value) {}

    @NotNull
    @Override
    public List<RangeMarker> getGuardedBlocks() {
      return Collections.emptyList();
    }

    @Override
    public boolean processRangeMarkers(@NotNull Processor<? super RangeMarker> processor) {
      return myRangeMarkers.process(processor);
    }

    @Override
    public boolean processRangeMarkersOverlappingWith(
        int start, int end, @NotNull Processor<? super RangeMarker> processor) {
      return myRangeMarkers.processOverlappingWith(start, end, processor);
    }

    @NotNull
    @Override
    public String getText() {
      return myString;
    }

    @NotNull
    @Override
    public String getText(@NotNull TextRange range) {
      return range.substring(getText());
    }

    @NotNull
    @Override
    public CharSequence getCharsSequence() {
      return myString;
    }

    @NotNull
    @Override
    public CharSequence getImmutableCharSequence() {
      return getText();
    }

    @NotNull
    @Override
    public char[] getChars() {
      return myChars;
    }

    @Override
    public int getTextLength() {
      return myChars.length;
    }

    @Override
    public int getLineCount() {
      return myLineSet.findLineIndex(myChars.length) + 1;
    }

    @Override
    public int getLineNumber(int offset) {
      return myLineSet.findLineIndex(offset);
    }

    @Override
    public int getLineStartOffset(int line) {
      return myChars.length == 0 ? 0 : myLineSet.getLineStart(line);
    }

    @Override
    public int getLineEndOffset(int line) {
      return myChars.length == 0 ? 0 : myLineSet.getLineEnd(line);
    }

    @Override
    public void insertString(int offset, @NotNull CharSequence s) {}

    @Override
    public void deleteString(int startOffset, int endOffset) {}

    @Override
    public void replaceString(int startOffset, int endOffset, @NotNull CharSequence s) {}

    @Override
    public boolean isWritable() {
      return false;
    }

    @Override
    public long getModificationStamp() {
      return 0;
    }

    @Override
    public void fireReadOnlyModificationAttempt() {}

    @Override
    public void addDocumentListener(@NotNull DocumentListener listener) {}

    @Override
    public void addDocumentListener(
        @NotNull DocumentListener listener, @NotNull Disposable parentDisposable) {}

    @Override
    public void removeDocumentListener(@NotNull DocumentListener listener) {}

    @NotNull
    @Override
    public RangeMarker createRangeMarker(int startOffset, int endOffset) {
      return new RangeMarkerImpl(this, startOffset, endOffset, true) {};
    }

    @NotNull
    @Override
    public RangeMarker createRangeMarker(
        int startOffset, int endOffset, boolean surviveOnExternalChange) {
      return null;
    }

    @Override
    public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {}

    @Override
    public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {}

    @Override
    public void setReadOnly(boolean isReadOnly) {}

    @NotNull
    @Override
    public RangeMarker createGuardedBlock(int startOffset, int endOffset) {
      return null;
    }

    @Override
    public void removeGuardedBlock(@NotNull RangeMarker block) {}

    @Nullable
    @Override
    public RangeMarker getOffsetGuard(int offset) {
      return null;
    }

    @Nullable
    @Override
    public RangeMarker getRangeGuard(int start, int end) {
      return null;
    }

    @Override
    public void startGuardedBlockChecking() {}

    @Override
    public void stopGuardedBlockChecking() {}

    @Override
    public void setCyclicBufferSize(int bufferSize) {}

    @NotNull
    @Override
    public RangeMarker createRangeMarker(@NotNull TextRange textRange) {
      return null;
    }

    @Override
    public int getLineSeparatorLength(int line) {
      return 0;
    }

    @Override
    public int getModificationSequence() {
      return 0;
    }
  }
}