/* * Geotoolkit.org - An Open Source Java GIS Toolkit * http://www.geotoolkit.org * * (C) 2005-2012, Open Source Geospatial Foundation (OSGeo) * (C) 2009-2012, Geomatys * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; * version 2.1 of the License. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. */ package org.geotoolkit.gui.swing.referencing; import java.util.Set; import java.util.Arrays; import java.util.List; import java.util.Locale; import java.util.Objects; import java.util.concurrent.ExecutionException; import javax.swing.SwingWorker; import javax.swing.AbstractListModel; import org.opengis.util.FactoryException; import org.opengis.referencing.AuthorityFactory; import org.opengis.referencing.IdentifiedObject; import org.apache.sis.util.logging.Logging; import org.geotoolkit.internal.swing.FastComboBox; /** * A list of {@link AuthorityCode}s. This implementation will try to fetch the codes only * when first needed. Keep in mind that the collection provided to the constructor may be * database backed (not a usual implementation from {@code java.util} package), so it is * worth to do lazy loading here. * * @author Martin Desruisseaux (IRD, Geomatys) * @version 3.12 * * @since 2.3 * @module */ @SuppressWarnings("serial") // Actually not serializable because AuthorityCode is not. final class AuthorityCodeList extends AbstractListModel<AuthorityCode> implements FastComboBox.Model<AuthorityCode> { /** * The authority codes as {@link AuthorityCode} objects. */ private AuthorityCode[] codes; /** * The selected item, or {@code null} if none. */ private AuthorityCode selected; /** * The code of the selected element. This is used when the selected element may * not yet be built by the background thread. Otherwise this is {@code null}. */ private transient String selectedCode; /** * The number of elements in this list. */ private int size; /** * Information needed for refreshing the list of authority codes * while we load them in a background thread. */ private static final class Step { final AuthorityCode[] codes; final int size; Step(final AuthorityCode[] codes, final int size) { this.codes = codes; this.size = size; } } /** * Creates a list for the given codes. * * @param The locale for formatting the code descriptions. * @param factory The factory to use for fetching the codes. * @param type Base classes of CRS objects to extract. */ @SafeVarargs public AuthorityCodeList(final Locale locale, final AuthorityFactory factory, final Class<? extends IdentifiedObject>... types) { new SwingWorker<Step,Step>() { /** * Gets the authority code in a background thread. Note that the iterator * returned by factory.getAuthorityCode(type) may be backed by a JCBC ResultSet. */ @Override protected Step doInBackground() throws FactoryException { int count = 0; AuthorityCode[] codes = new AuthorityCode[256]; for (final Class<? extends IdentifiedObject> type : types) { final Set<String> ic = factory.getAuthorityCodes(type); // Don't invoke 'ic.size()' because it may be costly. for (final String code : ic) { if (count == codes.length) { codes = Arrays.copyOf(codes, count*2); } codes[count] = new AuthorityCode(factory, code, count, locale); if ((++count & 0xFF) == 0) { // Report progress. publish(new Step(codes, count)); } } } return new Step(codes, count); } /** * Invoked in the Swing thread when new codes have been added in the list * by the background thread. */ private void process(final Step step) { final int lower = AuthorityCodeList.this.size; final int upper = step.size - 1; AuthorityCodeList.this.codes = step.codes; AuthorityCodeList.this.size = step.size; fireIntervalAdded(AuthorityCodeList.this, lower, upper); } /** * Invoked in the Swing thread when new codes have been added in the list * by the background thread. Only the last element from the chunk is used, * on the assumption that it is the most recent. */ @Override protected void process(final List<Step> chunk) { process(chunk.get(chunk.size() - 1)); } /** * Invoked in the Swing thread when the background thread finished its work. * If case of failure, the list will be incomplete but the combox box will * otherwise works as expected. */ @Override protected void done() { try { process(get()); } catch (InterruptedException e) { // Probably a cancelation, so stop the process. Logging.recoverableException(null, AuthorityCodeList.class, "<init>", e); } catch (ExecutionException e) { Logging.unexpectedException(null, AuthorityCodeList.class, "<init>", e.getCause()); } setSelectedCode(selectedCode); } }.execute(); } /** * Returns the length of the list. */ @Override public int getSize() { return size; } /** * Returns the value at the specified index. */ @Override public AuthorityCode getElementAt(final int index) { return (index >= 0 && index < size) ? codes[index] : null; } /** * Returns the selected item. */ @Override public AuthorityCode getSelectedItem() { return selected; } /** * Sets the selected item. */ @Override public void setSelectedItem(final Object code) { if (!Objects.equals(code, selected)) { selected = (AuthorityCode) code; selectedCode = null; final int index = (selected != null) ? selected.index : -1; fireContentsChanged(this, index, index); } } /** * Sets the selected item to the {@code AuthorityCode} element inferred from * the given code. This method does nothing if the given code is null. */ void setSelectedCode(final String code) { if (code != null) { final AuthorityCode[] codes = this.codes; final int size = this.size; for (int i=0; i<size; i++) { final AuthorityCode candidate = codes[i]; if (code.equals(candidate.code)) { setSelectedItem(candidate); return; } } } // If the SwingWorker thread is still running, we will search // for the code when the worker will have finished its job. selected = null; selectedCode = code; } /** * Returns the index of the currently selected element, or -1 if none. */ @Override public int getSelectedIndex() { return (selected != null) ? selected.index : -1; } }