All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.netbeans.modules.jumpto.common.Models Maven / Gradle / Ivy

There is a newer version: RELEASE113
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

package org.netbeans.modules.jumpto.common;

import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.atomic.AtomicReference;
import javax.swing.AbstractListModel;
import javax.swing.ListModel;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.event.ListDataEvent;
import javax.swing.event.ListDataListener;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.annotations.common.NullUnknown;
import org.netbeans.spi.jumpto.support.AsyncDescriptor;
import org.netbeans.spi.jumpto.support.DescriptorChangeEvent;
import org.netbeans.spi.jumpto.support.DescriptorChangeListener;
import org.openide.util.ChangeSupport;
import org.openide.util.Pair;

/**
 * (copied from org.netbeans.modules.java.source.util.Models
 * @author Petr Hrebejk, Tomas Zezula
 */
public final class Models {

    private  Models() {
    }


    public static  ListModel fromList(
            @NonNull final List list,
            @NullAllowed final Filter filter,
            @NullAllowed final Factory> attrCopier) {
        return new ListListModel<>(list, filter, attrCopier);
    }

    /** Creates list model which translates the objects using a factory.
     */
    public static  ListModel translating( ListModel model, Factory factory ) {
        return new TranslatingListModel( model, factory );
    }

    public static  ListModel refreshable(
            @NonNull final ListModel

model, @NonNull Factory> convertor) { return new RefreshableListModel(model, convertor); } public static MutableListModel mutable( @NullAllowed final Comparator comparator, @NullAllowed final Filter filter, @NullAllowed final Factory> attrCopier) { return new MutableListModelImpl(comparator, filter, attrCopier); } @NonNull public static Filter chained(@NonNull final Filter... filters) { return new ChainedFilter(filters); } // Exported types public interface MutableListModel extends ListModel { public void add(@NonNull Collection values); public void remove (@NonNull Collection values); public void clear(); } public interface Filter { public boolean accept(@NonNull T item); public void addChangeListener(@NonNull ChangeListener listener); public void remmoveChangeListener(@NonNull ChangeListener listener); } // Private innerclasses ---------------------------------------------------- private static final class ListListModel extends AbstractListModel implements ChangeListener, DescriptorChangeListener { private final List list; private final Filter filter; private final Factory> attrCopier; private List included; /** Creates a new instance of IteratorList */ public ListListModel( @NonNull final List list, @NullAllowed final Filter filter, @NullAllowed final Factory> attrCopier) { //Defensive copy and add listenener this.list = new ArrayList<>(list.size()); for (T item : list) { if (item instanceof AsyncDescriptor) { ((AsyncDescriptor)item).addDescriptorChangeListener(this); } this.list.add(item); } this.included = this.list; this.filter = filter; if (this.filter != null) { this.filter.addChangeListener(this); } this.attrCopier = attrCopier; } // List implementataion ------------------------------------------------ @Override public T getElementAt(int index) { assert SwingUtilities.isEventDispatchThread(); return included.get( index ); } @Override public int getSize() { assert SwingUtilities.isEventDispatchThread(); return included.size(); } @Override public void stateChanged(ChangeEvent e) { filterData(); } @Override public void descriptorChanged(@NonNull final DescriptorChangeEvent event) { final T source = (T) event.getSource(); final Collection items = copyAttrs(attrCopier, source, filter(this.filter, event.getReplacement())); ((AsyncDescriptor)source).removeDescriptorChangeListener(this); final Runnable r = new Runnable() { @Override public void run() { int listIndex = list.indexOf(source); int includedIndex = included.indexOf(source); if (listIndex >= 0) { switch (items.size()) { case 0: { list.remove(listIndex); if (includedIndex >= 0) { if (included != list) { included.remove(includedIndex); } fireIntervalRemoved(ListListModel.this, includedIndex, includedIndex); } break; } case 1: { final T item = head(items); list.set(listIndex, item); if (includedIndex >= 0) { if (included != list) { included.set(includedIndex, item); } fireContentsChanged(ListListModel.this, includedIndex, includedIndex); } break; } default: { final T head = head(items); final Collection tail = tail(items); list.set(listIndex, head); list.addAll(listIndex+1, tail); if (includedIndex >= 0) { if (included != list) { included.set(includedIndex, head); included.addAll(includedIndex+1, tail); } fireContentsChanged(ListListModel.this, includedIndex, includedIndex); fireIntervalAdded(ListListModel.this, includedIndex+1, includedIndex+tail.size()); } } } } } }; if (SwingUtilities.isEventDispatchThread()) { r.run(); } else { SwingUtilities.invokeLater(r); } } private void filterData() { if (filter != null) { invokeInEDT( new Callable() { @Override public Void call() { assert SwingUtilities.isEventDispatchThread(); final int oldSize = included.size(); final List newIncluded = new ArrayList<>(); for (T item : list) { if (filter.accept(item)) { newIncluded.add(item); } } included = newIncluded; final int newSize = included.size(); fireContentsChanged(this, 0, Math.max(0,Math.min(oldSize - 1, newSize - 1))); if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize - 1); } else if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize - 1); } return null; } }); } } } private static class TranslatingListModel implements ListModel { private Factory factory; private ListModel listModel; /** Creates a new instance of IteratorList */ public TranslatingListModel( ListModel model, Factory factory ) { this.listModel = model; this.factory = factory; } // List implementataion ---------------------------------------------------- //@SuppressWarnings("xlint") public T getElementAt(int index) { @SuppressWarnings("unchecked") P original = (P)listModel.getElementAt( index ); return factory.create( original ); } public int getSize() { return listModel.getSize(); } public void removeListDataListener(javax.swing.event.ListDataListener l) { // Does nothing - unmodifiable } public void addListDataListener(javax.swing.event.ListDataListener l) { // Does nothing - unmodifiable } } private static final class RefreshableListModel extends AbstractListModel implements ListDataListener { private final ListModel delegate; private final Factory> convertor; private final Map cache; RefreshableListModel( @NonNull final ListModel delegate, @NonNull final Factory> convertor) { this.delegate = delegate; this.convertor = convertor; this.cache = new IdentityHashMap<>(); delegate.addListDataListener(this); } @Override public int getSize() { return delegate.getSize(); } @Override @SuppressWarnings("unchecked") public Object getElementAt(final int index) { if (index < 0 || index >= delegate.getSize()) { throw new IllegalArgumentException( String.format( "Invalid index: %d, model size: %d.", //NOI18M index, delegate.getSize())); } final P orig = (P) delegate.getElementAt(index); R result = cache.get(orig); if (result != null) { return result; } result = convertor.create(Pair.of( orig, new Runnable() { @Override public void run() { int index = -1; for (int i = 0; i < delegate.getSize(); i++) { if (orig == delegate.getElementAt(i)) { index = i; break; } } if (index != -1) { fireContentsChanged(RefreshableListModel.this, index, index); } } })); cache.put(orig,result); return result; } @Override public void intervalAdded(ListDataEvent e) { fireIntervalAdded(this, e.getIndex0(), e.getIndex1()); } @Override public void intervalRemoved(ListDataEvent e) { fireIntervalRemoved(this, e.getIndex0(), e.getIndex1()); } @Override public void contentsChanged(ListDataEvent e) { fireContentsChanged(this, e.getIndex0(), e.getIndex1()); } } private static final class MutableListModelImpl extends AbstractListModel implements MutableListModel, ChangeListener, DescriptorChangeListener { private final Comparator comparator; private final Filter filter; private final Factory> attrCopier; private List items; private List included; MutableListModelImpl( @NullAllowed final Comparator comparator, @NullAllowed final Filter filter, @NullAllowed final Factory> attrCopier) { this.comparator = comparator; this.filter = filter; this.attrCopier = attrCopier; items = included = Collections.emptyList(); if (this.comparator instanceof StateFullComparator) { ((StateFullComparator)this.comparator).addChangeListener(this); } if (this.filter != null) { this.filter.addChangeListener(this); } } @Override public int getSize() { assert SwingUtilities.isEventDispatchThread(); return included.size(); } @Override public T getElementAt(int index) { assert SwingUtilities.isEventDispatchThread(); return included.get(index); } @Override public void add(Collection values) { for (T value : values) { if (value instanceof AsyncDescriptor) { ((AsyncDescriptor)value).addDescriptorChangeListener(this); } } boolean success; do { final Pair,List> data = getData(); data.second().addAll(values); if (comparator != null) { Collections.sort(data.second(), comparator); } success = casData(data.first(), data.second()); } while (!success); } @Override public void remove(Collection values) { for (T value : values) { if (value instanceof AsyncDescriptor) { ((AsyncDescriptor)value).removeDescriptorChangeListener(this); } } boolean success; do { final Pair,List> data = getData(); data.second().removeAll(values); success = casData(data.first(), data.second()); } while (!success); } @Override public void clear() { boolean success; do { final Pair,List> data = getData(); data.second().clear(); success = casData(data.first(), data.second()); } while (!success); } @Override public void stateChanged(@NonNull final ChangeEvent e) { final Object source = e.getSource(); if (source == this.comparator) { add(Collections.emptyList()); } else if (source == this.filter) { filterData(); } } @Override public void descriptorChanged(@NonNull final DescriptorChangeEvent event) { final T source = (T) event.getSource(); final Collection replacement = copyAttrs(attrCopier, source, filter(this.filter, event.getReplacement())); ((AsyncDescriptor)source).removeDescriptorChangeListener(this); final Runnable r = new Runnable() { @Override public void run() { int listIndex = items.indexOf(source); int includedIndex = included.indexOf(source); if (listIndex >= 0) { switch (replacement.size()) { case 0: { items.remove(listIndex); if (includedIndex >= 0) { if (included != items) { included.remove(includedIndex); } fireIntervalRemoved(MutableListModelImpl.this, includedIndex, includedIndex); } break; } case 1: { final T item = head(replacement); items.set(listIndex, item); if (includedIndex >= 0) { if (included != items) { included.set(includedIndex, item); } fireContentsChanged(MutableListModelImpl.this, includedIndex, includedIndex); } break; } default: { final T head = head(replacement); final Collection tail = tail(replacement); items.set(listIndex, head); items.addAll(listIndex+1, tail); if (includedIndex >= 0) { if (included != items) { included.set(includedIndex, head); included.addAll(includedIndex+1, tail); } fireContentsChanged(MutableListModelImpl.this, includedIndex, includedIndex); fireIntervalAdded(MutableListModelImpl.this, includedIndex+1, includedIndex+tail.size()); } } } } } }; if (SwingUtilities.isEventDispatchThread()) { r.run(); } else { SwingUtilities.invokeLater(r); } } private Pair,List> getData() { return invokeInEDT(new Callable,List>>() { @Override public Pair, List> call() throws Exception { assert SwingUtilities.isEventDispatchThread(); final List copy = new ArrayList<>(items); return Pair.,List>of(items, copy); } }); } private boolean casData(final List expected, final List update) { return invokeInEDT(new Callable() { @Override public Boolean call() throws Exception { assert SwingUtilities.isEventDispatchThread(); if (items == expected) { int oldSize = items.size(); items = included = update; int newSize = items.size(); fireContentsChanged(this, 0, Math.max(0, Math.min(oldSize - 1, newSize - 1))); if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize - 1); } else if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize - 1); } return true; } else { return false; } } }); } private void filterData() { if (filter != null) { invokeInEDT(new Callable() { @Override public Void call() { assert SwingUtilities.isEventDispatchThread(); final int oldSize = included.size(); List newIncluded = new ArrayList<>(items.size()); for (T item : items) { if (filter.accept(item)) { newIncluded.add(item); } } included = newIncluded; final int newSize = included.size(); fireContentsChanged(this, 0, Math.max(0, Math.min(oldSize - 1, newSize - 1))); if (oldSize < newSize) { fireIntervalAdded(this, oldSize, newSize - 1); } else if (oldSize > newSize) { fireIntervalRemoved(this, newSize, oldSize - 1); } return null; } }); } } } private static final class ChainedFilter implements Filter, ChangeListener { private final ChangeSupport changeSupport; private final Collection> filters; ChainedFilter(@NonNull final Filter... filters) { this.changeSupport = new ChangeSupport(this); this.filters = Arrays.asList(filters); for (Filter filter : this.filters) { filter.addChangeListener(this); } } @Override public boolean accept(@NonNull final T item) { for (Filter filter : filters) { if (!filter.accept(item)) { return false; } } return true; } @Override public void addChangeListener(@NonNull final ChangeListener listener) { changeSupport.addChangeListener(listener); } @Override public void remmoveChangeListener(@NonNull final ChangeListener listener) { changeSupport.removeChangeListener(listener); } @Override public void stateChanged(@NonNull final ChangeEvent e) { changeSupport.fireChange(); } } // Private helpers @NonNull private static Collection filter( @NullAllowed final Filter filter, @NonNull final Collection c) { if (filter == null) { return c; } final Collection res = new ArrayList<>(c.size()); for (T item : c) { if (filter.accept(item)) { res.add(item); } } return res; } @NonNull private static Collection copyAttrs( @NullAllowed final Factory> attrCopier, @NonNull final T source, @NonNull final Collection c) { if (attrCopier != null) { for (T item : c) { final T res = attrCopier.create(Pair.of(source, item)); assert res == item; } } return c; } @SuppressWarnings("unchecked") @NullUnknown private static T head(@NonNull final Collection c) { if (c instanceof List) { return ((List) c).get(0); } else { return c.iterator().next(); } } @SuppressWarnings("unchecked") @NonNull private static Collection tail(@NonNull final Collection c) { if (c instanceof List) { return ((List)c).subList(1, c.size()); } else { final List res = new ArrayList<>(c.size()-1); boolean add = false; for (T item : c) { if (add) { res.add(item); } else { add = true; } } return res; } } @NullUnknown private static R invokeInEDT(@NonNull final Callable call) { if (SwingUtilities.isEventDispatchThread()) { try { return call.call(); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new RuntimeException(e); } } else { try { final AtomicReference res = new AtomicReference<>(); SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { try { res.set(call.call()); } catch (Exception e) { throw new RuntimeException(e); } } }); return res.get(); } catch (InterruptedException | InvocationTargetException e) { throw new RuntimeException(e); } } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy