gt: 			specifyAttributes.add( XmlKeys.TIMEPOINTS_TIMEPOINT_TAG );
context: /*-
   * #%L
   * Fiji distribution of ImageJ for the life sciences.
   * %%
   * Copyright (C) 2007 - 2021 Fiji developers.
   * %%
   * This program is free software: you can redistribute it and/or modify
   * it under the terms of the GNU General Public License as
   * published by the Free Software Foundation, either version 2 of the
   * License, or (at your option) any later version.
   * 
   * This program 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 General Public License for more details.
   * 
   * You should have received a copy of the GNU General Public
   * License along with this program.  If not, see
   * <http://www.gnu.org/licenses/gpl-2.0.html>.
   * #L%
   */
  package spim.fiji.plugin.queryXML;
  
  import fiji.util.gui.GenericDialogPlus;
  import ij.gui.GenericDialog;
  
  import java.awt.Color;
  import java.awt.Label;
  import java.awt.TextField;
  import java.awt.event.ActionListener;
  import java.awt.event.TextEvent;
  import java.awt.event.TextListener;
  import java.text.ParseException;
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.Comparator;
  import java.util.Date;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  
  import javax.management.RuntimeErrorException;
  
  import mpicbg.spim.data.SpimDataException;
  import mpicbg.spim.data.Version;
  import mpicbg.spim.data.XmlKeys;
  import mpicbg.spim.data.generic.AbstractSpimData;
  import mpicbg.spim.data.generic.XmlIoAbstractSpimData;
  import mpicbg.spim.data.generic.base.Entity;
  import mpicbg.spim.data.generic.base.NamedEntity;
  import mpicbg.spim.data.generic.sequence.AbstractSequenceDescription;
  import mpicbg.spim.data.generic.sequence.BasicImgLoader;
  import mpicbg.spim.data.generic.sequence.BasicViewDescription;
  import mpicbg.spim.data.generic.sequence.BasicViewSetup;
  import mpicbg.spim.data.sequence.Angle;
  import mpicbg.spim.data.sequence.Channel;
  import mpicbg.spim.data.sequence.Illumination;
  import mpicbg.spim.data.sequence.TimePoint;
  import mpicbg.spim.io.IOFunctions;
  import spim.fiji.plugin.Toggle_Cluster_Options;
  import spim.fiji.plugin.util.GUIHelper;
  import spim.fiji.spimdata.EmptyEntity;
  import spim.fiji.spimdata.NamePattern;
  
  /**
   * Interface for interactive parsing of spimdata XMLs
   * 
   * @author Stephan Preibisch (stephan.preibisch@gmx.de)
   *
   * @param <AS>
   * @param <S>
   * @param <V>
   * @param <D>
   * @param <L>
   */
  public class GenericLoadParseQueryXML<
  		AS extends AbstractSpimData< S >,
  		S extends AbstractSequenceDescription< V, D, L >,
  		V extends BasicViewSetup,
  		D extends BasicViewDescription< V >,
  		L extends BasicImgLoader,
  		X extends XmlIoAbstractSpimData< S, AS > >
  {
  	public static String defaultXMLfilename = "";
  	public static boolean debugRandomClusterHash = false;
  
  	protected static String goodMsg1 = "The selected XML file was parsed successfully";
  	protected static String warningMsg1 = "The selected file does not appear to be an xml. Press OK to try to parse anyways.";
  	protected static String errorMsg1 = "An ERROR occured parsing this XML file! Please select a different XML (see log)";
  	protected static String neutralMsg1 = "No XML file selected.";	
  	protected static String noMsg2 = " ";
  	protected static String[] attributeChoiceList = new String[]{ "All *s", "Single * (Select from List)", "Multiple *s (Select from List)", "Range of *s (Specify by Name)" };
  
  	public static final String[] clusterOptions1 = new String[]{
  		"Do not process on cluster",
  		"Save every XML with unique id generated from processed subset",
  		"Save every XML with user-provided unique id" };
  
  	public static int defaultClusterOption1 = 1;
  
  	// remember as default
  	public static HashMap< String, Integer > defaultAttributeChoice = new HashMap< String, Integer >();
  	public static HashMap< String, Integer > defaultAttributeEntityIndex = new HashMap< String, Integer >();
  	public static HashMap< String, String > defaultAttributePatternString = new HashMap< String, String >();
  	public static HashMap< String, boolean[] > defaultAttributeSelectedEntities = new HashMap< String, boolean[] >();
  
  	// the current instance
  	protected HashMap< String, Integer > attributeChoice = new HashMap< String, Integer >();
  
  	// how to load the XML
  	final protected X io;
  
  	// how to sort the attributes (if not by name)
  	protected Comparator< String > comparator = null;
  
  	// local variables for display
  	protected String message1, message2;
  	protected Color color;
  	
  	// result variables
  	protected AS data;
  	protected String xmlfilename;
  	protected ArrayList< TimePoint > timepointsToProcess;
  	
  	// which attributes are there
  	protected ArrayList< String > attributes;
  	
  	// all instances of all entities per attribute
  	protected HashMap< String, List< Entity > > allAttributeInstances;
  
  	// all instances of all entities per attribute
  	protected HashMap< String, List< Entity > > attributeInstancesToProcess;
  
  	// extension for the XML when saving
  	protected String clusterExt = null;
  
  	// add a button on demand
  	protected String buttonText = null;
  	protected ActionListener listener = null;
  	protected GenericDialog gd = null;
  	protected boolean returnfalse = false;
  
  	/**
  	 * Constructor for the class needs an appropriate IO module
  	 * @param io
  	 */
  	public GenericLoadParseQueryXML( final X io )
  	{
  		this.io = io;
  		IOFunctions.println( "Using spimdata version: " + Version.getVersion() );
  		IOFunctions.println( "Using spimreconstruction version: " + spim.Version.getVersion() );
  	}
  
  	/**
  	 * @return the i/o object used to parse the XML
  	 */
  	public X getIO() { return io; }
  	
  	/**
  	 * @return the SpimData object parsed from the xml
  	 */
  	public AS getData() { return data; }
  	
  	/**
  	 * @return The location of the xml file
  	 */
  	public String getXMLFileName() { return xmlfilename; }
  	
  	/**
  	 * @return All timepoints that should be processed
  	 */
  	public List< TimePoint > getTimePointsToProcess() { return timepointsToProcess; }		
  
  	/**
  	 * @return All ViewSetups that should be processed per timepoint (based on the selected attributes)
  	 */
  	public ArrayList< V > getViewSetupsToProcess()
  	{
  		final ArrayList< V > viewSetups = new ArrayList< V >();
  
  		for ( final V v : data.getSequenceDescription().getViewSetupsOrdered() )
  		{
  			boolean contained = true;
  			
  			// check over all attributes if they are part of the attributeInstancesToProcess
  			for ( final String attribute : attributes )
  			{
  				final Entity attributeEntityForViewSetup = v.getAttributes().get( attribute );
  				
  				if ( !attributeInstancesToProcess.get( attribute ).contains( attributeEntityForViewSetup ) )
  					contained = false;
  			}
  			
  			if ( contained )
  				viewSetups.add( v );
  		}
  
  		return viewSetups;
  	}
  
  	/**
  	 * Sets the comparator used to sort the Attributes in a specific order for display, can be null
  	 * Note that Timepoint (XmlKeys.TIMEPOINTS_TIMEPOINT_TAG) is always the last entry no matter what as it is special
  	 * @param comparator
  	 */
  	public void setComparator( final Comparator< String > comparator ) { this.comparator = comparator; }
  	public Comparator< String > getComparator() { return comparator; }
  	
  	public boolean queryXML() { return queryXML( "", "Process", null ); } 
  	public boolean queryXML( final List< String > specifyAttributes ) { return queryXML( "", "Process", specifyAttributes ); } 
  	public boolean queryXML( final String query ) { return queryXML( "", query, null ); } 
  	public boolean queryXML( final String query, final List< String > specifyAttributes ) { return queryXML( "", query, specifyAttributes ); } 
  
  	public void addButton( final String buttonText, final ActionListener listener )
  	{
  		this.buttonText = buttonText;
  		this.listener = listener;
  	}
  
  	public GenericDialog getGenericDialog() { return gd; }
  	public void setReturnFalse( final boolean value ) { this.returnfalse = value; }
  
  	/**
  	 * Asks the user for a valid XML (real time parsing)
  	 * 
  	 * @param specifyAttributes - set of attributes the user should be asked if he/she wants to select a subset of them
  	 * @return null if cancelled or timepointlistsize = 0
  	 */
  	public boolean queryXML(
  			final String additionalTitle,
  			String query,
  			List< String > specifyAttributes )
  	{
  		// should not be null, just empty
  		if ( specifyAttributes == null )
  			specifyAttributes = new ArrayList< String >();
  		
  		// they are ordered by alphabet (or user defined) so that the details and then queried in the same order
  		if ( comparator == null )
  			Collections.sort( specifyAttributes );
  		else
  			Collections.sort( specifyAttributes, comparator );
  		
  		// timepoint is always last
  		if ( specifyAttributes.contains( XmlKeys.TIMEPOINTS_TIMEPOINT_TAG ) )
  		{
  			specifyAttributes.remove( XmlKeys.TIMEPOINTS_TIMEPOINT_TAG );
  