package org.netbeans.gradle.project.output; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.model.util.Exceptions; public final class IOTabMaintainer<TabKey, IOTab extends IOTabDef> { private final Lock mainLock; private final IOTabFactory<? extends IOTab> factory; private final Map<TabKey, List<CountedTab<IOTab>>> currentTabs; private final KeyCounter<TabKey> tabIndexes; public IOTabMaintainer(IOTabFactory<? extends IOTab> factory) { ExceptionHelper.checkNotNullArgument(factory, "factory"); this.mainLock = new ReentrantLock(); this.currentTabs = new HashMap<>(); this.factory = factory; this.tabIndexes = new KeyCounter<>(); } private CountedTab<IOTab> tryGetAvailable(TabKey key) { CountedTab<IOTab> result; do { mainLock.lock(); try { List<CountedTab<IOTab>> list = currentTabs.get(key); if (list == null) { return null; } result = null; for (CountedTab<IOTab> tab: list) { if (result == null || tab.index < result.index) { result = tab; } } // Should never happen in the current implementation because // empty lists are removed and the list does not contain // null elements. if (result == null) { return null; } list.remove(result); if (list.isEmpty()) { currentTabs.remove(key); } } finally { mainLock.unlock(); } } while (result.isClosed()); return result; } private Set<CountedTab<IOTab>> getTabsToClose() { List<CountedTab<IOTab>> allTabs = new ArrayList<>(); mainLock.lock(); try { for (List<CountedTab<IOTab>> tabs: currentTabs.values()) { allTabs.addAll(tabs); } } finally { mainLock.unlock(); } Set<CountedTab<IOTab>> result = new HashSet<>(); for (CountedTab<IOTab> tab: allTabs) { if (tab.isClosed()) { result.add(tab); } } return result; } private void cleanupTabs() { Set<CountedTab<IOTab>> toClose = getTabsToClose(); if (toClose.isEmpty()) { return; } mainLock.lock(); try { for (List<CountedTab<IOTab>> tabs: currentTabs.values()) { Iterator<CountedTab<IOTab>> tabsItr = tabs.iterator(); while (tabsItr.hasNext()) { CountedTab<IOTab> tab = tabsItr.next(); if (toClose.contains(tab)) { tabsItr.remove(); } } } Iterator<Map.Entry<TabKey, List<CountedTab<IOTab>>>> entryItr = currentTabs.entrySet().iterator(); while (entryItr.hasNext()) { Map.Entry<TabKey, List<CountedTab<IOTab>>> entry = entryItr.next(); if (entry.getValue().isEmpty()) { entryItr.remove(); } } } finally { mainLock.unlock(); } } private CountedTab<IOTab> newTabWithContext(TabKey key, String caption) { int index = tabIndexes.incAndGet(key); try { String captionWithIndex = index == 1 ? caption : caption + " #" + index; IOTab tab = factory.create(captionWithIndex); return new CountedTab<>(index, tab); } catch (Throwable ex) { tabIndexes.decAndGet(key); throw Exceptions.throwUnchecked(ex); } } public IOTabRef<IOTab> getNewTab(TabKey key, String caption) { ExceptionHelper.checkNotNullArgument(key, "key"); ExceptionHelper.checkNotNullArgument(caption, "caption"); CountedTab<IOTab> result = newTabWithContext(key, caption); return new IOTabRefImpl(key, result); } public IOTabRef<IOTab> getTab(TabKey key, String caption) { ExceptionHelper.checkNotNullArgument(key, "key"); ExceptionHelper.checkNotNullArgument(caption, "caption"); cleanupTabs(); CountedTab<IOTab> result = tryGetAvailable(key); if (result == null) { result = newTabWithContext(key, caption); } else { tabIndexes.incAndGet(key); } return new IOTabRefImpl(key, result); } private class IOTabRefImpl implements IOTabRef<IOTab> { private final TabKey key; private final CountedTab<IOTab> tab; private final AtomicBoolean closed; public IOTabRefImpl(TabKey key, CountedTab<IOTab> tab) { this.key = key; this.tab = tab; this.closed = new AtomicBoolean(false); } @Override public IOTab getTab() { return tab.tab; } @Override public void close() throws IOException { if (!closed.compareAndSet(false, true)) { return; } tabIndexes.decAndGet(key); mainLock.lock(); try { List<CountedTab<IOTab>> tabList = currentTabs.get(key); if (tabList == null) { tabList = new LinkedList<>(); currentTabs.put(key, tabList); } tabList.add(tab); } finally { mainLock.unlock(); // Fixes memory leak: #256355 (netbeans.org/bugzilla) // Also, removes the boldness from the caption of the output tab. getTab().close(); } } } private static final class CountedTab<IOTab extends IOTabDef> { public final int index; public final IOTab tab; public CountedTab(int index, IOTab tab) { this.index = index; this.tab = tab; } public boolean isClosed() { return tab.isDestroyed(); } } }