resolved conflicts and adapted to some upstream changes. no detailed functional testing as yet (see next commit :) )
Conflicts:
resources/lang/Messages.properties
resources/lang/Messages_es.properties
src/jalview/api/AlignViewportI.java
src/jalview/bin/Jalview.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceI.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/JvSwingUtils.java
src/jalview/gui/OverviewPanel.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SplitFrame.java
src/jalview/gui/WsJobParameters.java
src/jalview/io/AlignmentFileReaderI.java
src/jalview/io/FileLoader.java
src/jalview/io/StockholmFile.java
src/jalview/jbgui/GAlignFrame.java
src/jalview/jbgui/GPreferences.java
src/jalview/renderer/AnnotationRenderer.java
src/jalview/viewmodel/AlignmentViewport.java
src/jalview/ws/jws2/Jws2Discoverer.java
test/jalview/analysis/AAFrequencyTest.java
test/jalview/gui/AlignFrameTest.java
test/jalview/gui/AlignViewportTest.java
label.most_polymer_residues = Most Polymer Residues
label.cached_structures = Cached Structures
label.free_text_search = Free Text Search
+label.annotation_name = Annotation Name
+label.annotation_description = Annotation Description
+label.edit_annotation_name_description = Edit Annotation Name/Description
+label.alignment = alignment
+label.pca = PCA
+label.create_image_of = Create {0} image of {1}
+label.click_to_edit = Click to edit, right-click for menu
+ label.hmmalign = hmmalign
+ label.use_hmm = HMM profile to use
+ label.use_sequence = Sequence to use
+ label.hmmbuild = hmmbuild
+ label.hmmsearch = hmmsearch
+ label.jackhmmer = jackhmmer
+ label.installation = Installation
+ label.hmmer_location = HMMER Binaries Installation Location
+ label.cygwin_location = Cygwin Binaries Installation Location (Windows)
+ label.information_annotation = Information Annotation
+ label.ignore_below_background_frequency = Ignore Below Background Frequency
+ label.information_description = Information content, measured in bits
+ warn.no_hmm = No Hidden Markov model found.\nRun hmmbuild or load an HMM file first.
+ label.no_sequences_found = No matching sequences, or an error occurred.
+ label.hmmer = HMMER
+ label.trim_termini = Trim Non-Matching Termini
+ label.trim_termini_desc = If true, non-matching regions on either end of the resulting alignment are removed.
+ label.no_of_sequences = Number of sequences returned
+ label.reporting_cutoff = Reporting Cut-off
+ label.inclusion_threshold = Inlcusion Threshold
+ label.freq_alignment = Use alignment background frequencies
+ label.freq_uniprot = Use Uniprot background frequencies
+ label.hmmalign_options = hmmalign options
+ label.hmmsearch_options = hmmsearch options
+ label.jackhmmer_options = jackhmmer options
+ label.executable_not_found = The ''{0}'' executable file was not found
+ warn.command_failed = {0} failed
+ label.invalid_folder = Invalid Folder
+ label.number_of_results = Number of Results to Return
+ label.number_of_iterations = Number of jackhmmer Iterations
+ label.auto_align_seqs = Automatically Align Fetched Sequences
+ label.new_returned = new sequences returned
+ label.use_accessions = Return Accessions
+ label.check_for_new_sequences = Return Number of New Sequences
+ label.evalue = E-Value
+ label.reporting_seq_evalue = Reporting Sequence E-value Cut-off
+ label.reporting_seq_score = Reporting Sequence Score Threshold
+ label.reporting_dom_evalue = Reporting Domain E-value Cut-off
+ label.reporting_dom_score = Reporting Domain Score Threshold
+ label.inclusion_seq_evalue = Inclusion Sequence E-value Cut-off
+ label.inclusion_seq_score = Inclusion Sequence Score Threshold
+ label.inclusion_dom_evalue = Inclusion Domain E-value Cut-off
+ label.inclusion_dom_score = Inclusion Domain Score Threshold
+ label.number_of_results_desc = The maximum number of hmmsearch results to display
+ label.number_of_iterations_desc = The number of iterations jackhmmer will complete when searching for new sequences
+ label.auto_align_seqs_desc = If true, all fetched sequences will be aligned to the hidden Markov model with which the search was performed
+ label.check_for_new_sequences_desc = Display number of new sequences returned from hmmsearch compared to the previous alignment
+ label.use_accessions_desc = If true, the accession number of each sequence is returned, rather than that sequence's name
+ label.reporting_seq_e_value_desc = The E-value cutoff for returned sequences
+ label.reporting_seq_score_desc = The score threshold for returned sequences
+ label.reporting_dom_e_value_desc = The E-value cutoff for returned domains
+ label.reporting_dom_score_desc = The score threshold for returned domains
+ label.inclusion_seq_e_value_desc = Sequences with an E-value less than this cut-off are classed as significant
+ label.inclusion_seq_score_desc = Sequences with a bit score greater than this threshold are classed as significant
+ label.inclusion_dom_e_value_desc = Domains with an E-value less than this cut-off are classed as significant
+ label.inclusion_dom_score_desc = Domains with a bit score greater than this threshold are classed as significant
+ label.add_database = Add Database
+ label.this_alignment = This alignment
+ warn.invalid_format = This is not a valid database file format. The current supported formats are Fasta, Stockholm and Pfam.
+ label.database_for_hmmsearch = The database hmmsearch will search through
+ label.use_reference = Use Reference Annotation
+ label.use_reference_desc = If true, hmmbuild will keep all columns defined as a reference position by the reference annotation
+ label.hmm_name = Alignment HMM Name
+ label.hmm_name_desc = The name given to the HMM for the alignment
+ warn.no_reference_annotation = No reference annotation found
+ label.hmmbuild_for = Build HMM for
+ label.hmmbuild_for_desc = Build an HMM for the selected sets of sequences
+ label.alignment = Alignment
+ label.groups_and_alignment = All groups and alignment
+ label.groups = All groups
+ label.selected_group = Selected group
+ label.use_info_for_height = Use Information Content as Letter Height
+ action.search = Search
label.backupfiles_confirm_delete = Confirm delete
label.backupfiles_confirm_delete_old_files = Delete the following older backup files? (see the Backups tab in Preferences for more options)
label.backupfiles_confirm_save_file = Confirm save file
label.most_polymer_residues = Más Residuos de PolÃmeros
label.cached_structures = Estructuras en Caché
label.free_text_search = Búsqueda de texto libre
+label.annotation_name = Nombre de la anotación
+label.annotation_description = Descripción de la anotación
+label.edit_annotation_name_description = Editar el nombre/descripción de la anotación
+label.alignment = alineamiento
+label.pca = ACP
+label.create_image_of = Crear imagen {0} de {1}
+label.click_to_edit = Haga clic para editar, clic en el botón derecho para ver el menú
+ action.search = Buscar
label.backupfiles_confirm_delete = Confirmar borrar
label.backupfiles_confirm_delete_old_files = ¿Borrar los siguientes archivos? (ver la pestaña 'Copias' de la ventana de Preferencias para más opciones)
label.backupfiles_confirm_save_file = Confirmar guardar archivo
import jalview.datamodel.AlignmentI;
import jalview.datamodel.Annotation;
import jalview.datamodel.DBRefEntry;
--import jalview.datamodel.DBRefSource;
import jalview.datamodel.FeatureProperties;
import jalview.datamodel.GraphLine;
import jalview.datamodel.Mapping;
@Override
void setProteinFontAsCdna(boolean b);
- TreeModel getCurrentTree();
+ void setHmmProfiles(ProfilesI info);
- void setCurrentTree(TreeModel tree);
+ ProfilesI getHmmProfiles();
+
+ /**
+ * Registers and starts a worker thread to calculate Information Content
+ * annotation, if it is not already registered
+ *
+ * @param ap
+ */
+ void initInformationWorker(AlignmentViewPanel ap);
+
+ boolean isInfoLetterHeight();
+
- abstract TreeModel getCurrentTree();
++ public abstract TreeModel getCurrentTree();
- abstract void setCurrentTree(TreeModel tree);
+ /**
+ * Answers a data bean containing data for export as configured by the
+ * supplied options
+ *
+ * @param options
+ * @return
+ */
+ AlignmentExportData getAlignExportData(AlignExportSettingsI options);
+
++ public abstract void setCurrentTree(TreeModel tree);
+
/**
* @param update
* - set the flag for updating structures on next repaint
public jalview.bin.JalviewLite applet;
- boolean MAC = false;
-
private AnnotationColumnChooser annotationColumnSelectionState;
+ java.awt.Frame nullFrame;
+
+ protected FeatureSettings featureSettings = null;
+
+ private float heightScale = 1, widthScale = 1;
+
public AlignViewport(AlignmentI al, JalviewLite applet)
{
super(al);
import jalview.schemes.ResidueProperties;
import jalview.util.Comparison;
import jalview.util.MessageManager;
--import jalview.util.Platform;
import jalview.viewmodel.ViewportListenerI;
import jalview.viewmodel.ViewportRanges;
*/
package jalview.appletgui;
--import java.awt.Frame;
import java.awt.Graphics;
import java.awt.Insets;
--import java.awt.Label;
import java.awt.Panel;
--import java.awt.event.WindowAdapter;
--import java.awt.event.WindowEvent;
++
public class TitledPanel extends Panel
{
static
{
- // grab all the rights we can the JVM
- Policy.setPolicy(new Policy()
+ if (!Platform.isJS())
+ /**
+ * Java only
+ *
+ * @j2sIgnore
+ */
{
- @Override
- public PermissionCollection getPermissions(CodeSource codesource)
- {
- Permissions perms = new Permissions();
- perms.add(new AllPermission());
- return (perms);
- }
-
- @Override
- public void refresh()
+ // grab all the rights we can for the JVM
- Policy.setPolicy(new Policy()
- {
- @Override
- public PermissionCollection getPermissions(CodeSource codesource)
- {
- Permissions perms = new Permissions();
- perms.add(new AllPermission());
- return (perms);
- }
-
- @Override
- public void refresh()
- {
- }
- });
++ Policy.setPolicy(new Policy()
+ {
- }
- });
++ @Override
++ public PermissionCollection getPermissions(CodeSource codesource)
++ {
++ Permissions perms = new Permissions();
++ perms.add(new AllPermission());
++ return (perms);
++ }
++
++ @Override
++ public void refresh()
++ {
++ }
++ });
+ }
}
/**
*/
public static void main(String[] args)
{
- // setLogging(); // BH - for event debugging in JavaScript
++// setLogging(); // BH - for event debugging in JavaScript
instance = new Jalview();
instance.doMain(args);
+}
+
+ private static void logClass(String name)
- {
- // BH - for event debugging in JavaScript
++ {
++ // BH - for event debugging in JavaScript
+ ConsoleHandler consoleHandler = new ConsoleHandler();
+ consoleHandler.setLevel(Level.ALL);
+ Logger logger = Logger.getLogger(name);
+ logger.setLevel(Level.ALL);
+ logger.addHandler(consoleHandler);
}
+ @SuppressWarnings("unused")
+ private static void setLogging()
+ {
+
+ /**
+ * @j2sIgnore
+ *
+ */
+ {
+ System.out.println("not in js");
+ }
+
- // BH - for event debugging in JavaScript (Java mode only)
++ // BH - for event debugging in JavaScript (Java mode only)
+ if (!Platform.isJS())
+ /**
+ * Java only
+ *
+ * @j2sIgnore
+ */
- {
- Logger.getLogger("").setLevel(Level.ALL);
++ {
++ Logger.getLogger("").setLevel(Level.ALL);
+ logClass("java.awt.EventDispatchThread");
+ logClass("java.awt.EventQueue");
+ logClass("java.awt.Component");
+ logClass("java.awt.focus.Component");
+ logClass("java.awt.focus.DefaultKeyboardFocusManager");
- }
++ }
+
+ }
+
+
+
+
/**
* @param args
*/
private char[] sequence;
- String description;
+ private String description;
+
+ private int start;
+
+ private int end;
- int start;
+ private Vector<PDBEntry> pdbIds;
- int end;
+ private String vamsasId;
+ HiddenMarkovModel hmm;
+
+ boolean isHMMConsensusSequence = false;
+
- Vector<PDBEntry> pdbIds;
+ private DBModList<DBRefEntry> dbrefs; // controlled access
- String vamsasId;
-
- DBRefEntry[] dbrefs;
+ /**
+ * a flag to let us know that elements have changed in dbrefs
+ *
+ * @author Bob Hanson
+ */
+ private int refModCount = 0;
- RNA rna;
+ private RNA rna;
/**
* This annotation is displayed below the alignment but the positions are tied
* iterator over regions
* @return first residue not contained in regions
*/
- int firstResidueOutsideIterator(Iterator<int[]> it);
++
+ public int firstResidueOutsideIterator(Iterator<int[]> it);
+
+ /**
+ * Answers true if this sequence has an associated Hidden Markov Model
+ *
+ * @return
+ */
+ boolean hasHMMProfile();
}
+
import jalview.datamodel.SequenceI;
import jalview.io.gff.SequenceOntologyI;
import jalview.util.JSONUtils;
- import jalview.util.Platform;
--import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.DBRefSource;
- import jalview.util.JSONUtils;
--import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.DBRefEntry;
import jalview.util.DBRefUtils;
- import jalview.util.JSONUtils;
--import java.io.BufferedReader;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
*/
package jalview.gui;
+import java.awt.BorderLayout;
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Rectangle;
+import java.awt.Toolkit;
+import java.awt.datatransfer.Clipboard;
+import java.awt.datatransfer.DataFlavor;
+import java.awt.datatransfer.StringSelection;
+import java.awt.datatransfer.Transferable;
+import java.awt.dnd.DnDConstants;
+import java.awt.dnd.DropTargetDragEvent;
+import java.awt.dnd.DropTargetDropEvent;
+import java.awt.dnd.DropTargetEvent;
+import java.awt.dnd.DropTargetListener;
+import java.awt.event.ActionEvent;
+import java.awt.event.ActionListener;
+import java.awt.event.FocusAdapter;
+import java.awt.event.FocusEvent;
+import java.awt.event.ItemEvent;
+import java.awt.event.ItemListener;
+import java.awt.event.KeyAdapter;
+import java.awt.event.KeyEvent;
+import java.awt.event.MouseEvent;
+import java.awt.print.PageFormat;
+import java.awt.print.PrinterJob;
+import java.beans.PropertyChangeEvent;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.PrintWriter;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Deque;
- import java.util.Enumeration;
- import java.util.Hashtable;
+import java.util.List;
+import java.util.Vector;
+
+import javax.swing.ButtonGroup;
+import javax.swing.JCheckBoxMenuItem;
+import javax.swing.JComponent;
+import javax.swing.JEditorPane;
+import javax.swing.JInternalFrame;
+import javax.swing.JLabel;
+import javax.swing.JLayeredPane;
+import javax.swing.JMenu;
+import javax.swing.JMenuItem;
+import javax.swing.JPanel;
+import javax.swing.JScrollPane;
+import javax.swing.SwingUtilities;
+
+import ext.vamsas.ServiceHandle;
import jalview.analysis.AlignmentSorter;
import jalview.analysis.AlignmentUtils;
import jalview.analysis.CrossRef;
import jalview.viewmodel.ViewportRanges;
import jalview.ws.DBRefFetcher;
import jalview.ws.DBRefFetcher.FetchFinishedListenerI;
+ import jalview.ws.api.ServiceWithParameters;
import jalview.ws.jws1.Discoverer;
import jalview.ws.jws2.Jws2Discoverer;
- import jalview.ws.jws2.jabaws2.Jws2Instance;
+ import jalview.ws.params.ArgumentI;
+ import jalview.ws.params.ParamDatastoreI;
+ import jalview.ws.params.WsParamSetI;
import jalview.ws.seqfetcher.DbSourceProxy;
+ import jalview.ws.slivkaws.SlivkaWSDiscoverer;
-
-import java.awt.BorderLayout;
-import java.awt.Component;
-import java.awt.Rectangle;
-import java.awt.Toolkit;
-import java.awt.datatransfer.Clipboard;
-import java.awt.datatransfer.DataFlavor;
-import java.awt.datatransfer.StringSelection;
-import java.awt.datatransfer.Transferable;
-import java.awt.dnd.DnDConstants;
-import java.awt.dnd.DropTargetDragEvent;
-import java.awt.dnd.DropTargetDropEvent;
-import java.awt.dnd.DropTargetEvent;
-import java.awt.dnd.DropTargetListener;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
-import java.awt.event.FocusAdapter;
-import java.awt.event.FocusEvent;
-import java.awt.event.ItemEvent;
-import java.awt.event.ItemListener;
-import java.awt.event.KeyAdapter;
-import java.awt.event.KeyEvent;
-import java.awt.event.MouseEvent;
-import java.awt.print.PageFormat;
-import java.awt.print.PrinterJob;
-import java.beans.PropertyChangeEvent;
-import java.io.File;
-import java.io.FileWriter;
+ import java.io.IOException;
-import java.io.PrintWriter;
-import java.net.URL;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Deque;
+ import java.util.HashSet;
-import java.util.List;
+ import java.util.Set;
-import java.util.Vector;
+
-import javax.swing.ButtonGroup;
-import javax.swing.JCheckBoxMenuItem;
-import javax.swing.JEditorPane;
+ import javax.swing.JFileChooser;
-import javax.swing.JInternalFrame;
-import javax.swing.JLayeredPane;
-import javax.swing.JMenu;
-import javax.swing.JMenuItem;
+ import javax.swing.JOptionPane;
-import javax.swing.JScrollPane;
-import javax.swing.SwingUtilities;
/**
* DOCUMENT ME!
*/
String fileName = null;
++ /**
++ * TODO: remove reference to 'FileObject' in AlignFrame - not correct mapping
++ */
+ File fileObject;
/**
* Creates a new AlignFrame object with specific width and height.
@Override
public void associatedData_actionPerformed(ActionEvent e)
+ throws IOException, InterruptedException
{
- // Pick the tree file
- JalviewFileChooser chooser = new JalviewFileChooser(
+ final JalviewFileChooser chooser = new JalviewFileChooser(
jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
chooser.setFileView(new JalviewFileView());
- chooser.setDialogTitle(
- MessageManager.getString("label.load_jalview_annotations"));
- chooser.setToolTipText(
- MessageManager.getString("label.load_jalview_annotations"));
-
- int value = chooser.showOpenDialog(null);
-
- if (value == JalviewFileChooser.APPROVE_OPTION)
+ String tooltip = MessageManager.getString("label.load_jalview_annotations");
+ chooser.setDialogTitle(tooltip);
+ chooser.setToolTipText(tooltip);
+ chooser.setResponseHandler(0, new Runnable()
{
- String choice = chooser.getSelectedFile().getPath();
- jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
- loadJalviewDataFile(choice, null, null, null);
- }
+ @Override
+ public void run()
+ {
+ String choice = chooser.getSelectedFile().getPath();
+ jalview.bin.Cache.setProperty("LAST_DIRECTORY", choice);
+ loadJalviewDataFile(chooser.getSelectedFile(), null, null, null);
+ }
+ });
+ chooser.showOpenDialog(this);
}
/**
*
* @param file
* either a filename or a URL string.
+ * @throws InterruptedException
+ * @throws IOException
*/
- public void loadJalviewDataFile(String file, DataSourceType sourceType,
+ public void loadJalviewDataFile(Object file, DataSourceType sourceType,
FileFormatI format, SequenceI assocSeq)
{
+ // BH 2018 was String file
try
{
if (sourceType == null)
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.gui.ColourMenuHelper.ColourChangeListener;
+import jalview.gui.JalviewColourChooser.ColourChooserListener;
+ import jalview.io.CountReader;
import jalview.io.FileFormatI;
import jalview.io.FileFormats;
import jalview.io.FormatAdapter;
import jalview.renderer.ResidueShaderI;
import jalview.util.MessageManager;
--import java.awt.event.ActionEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.beans.PropertyVetoException;
{
return featureSettingsUI != null && !featureSettingsUI.isClosed();
}
--}
++}
paramPane.revalidate();
revalidate();
}
--
- /**
- * testing method - grab a service and parameter set and show the window
- *
- * @param args
- * @j2sIgnore
- */
- public static void main(String[] args)
- {
- jalview.ws.jws2.Jws2Discoverer disc = jalview.ws.jws2.Jws2Discoverer
- .getDiscoverer();
- int p = 0;
- if (args.length > 0)
- {
- Vector<String> services = new Vector<>();
- services.addElement(args[p++]);
- Jws2Discoverer.getDiscoverer().setServiceUrls(services);
- }
- try
- {
- disc.run();
- } catch (Exception e)
- {
- System.err.println("Aborting. Problem discovering services.");
- e.printStackTrace();
- return;
- }
- Jws2Instance lastserv = null;
- for (Jws2Instance service : disc.getServices())
- {
- lastserv = service;
- if (p >= args.length || service.serviceType.equalsIgnoreCase(args[p]))
- {
- if (lastserv != null)
- {
- List<Preset> prl = null;
- Preset pr = null;
- if (++p < args.length)
- {
- PresetManager prman = lastserv.getPresets();
- if (prman != null)
- {
- pr = prman.getPresetByName(args[p]);
- if (pr == null)
- {
- // just grab the last preset.
- prl = prman.getPresets();
- }
- }
- }
- else
- {
- PresetManager prman = lastserv.getPresets();
- if (prman != null)
- {
- prl = prman.getPresets();
- }
- }
- Iterator<Preset> en = (prl == null) ? null : prl.iterator();
- while (en != null && en.hasNext())
- {
- if (en != null)
- {
- if (!en.hasNext())
- {
- en = prl.iterator();
- }
- pr = en.next();
- }
- {
- System.out.println("Testing opts dupes for "
- + lastserv.getUri() + " : " + lastserv.getActionText()
- + ":" + pr.getName());
- List<Option> rg = lastserv.getRunnerConfig().getOptions();
- for (Option o : rg)
- {
- try
- {
- Option cpy = jalview.ws.jws2.ParameterUtils.copyOption(o);
- } catch (Exception e)
- {
- System.err.println("Failed to copy " + o.getName());
- e.printStackTrace();
- } catch (Error e)
- {
- System.err.println("Failed to copy " + o.getName());
- e.printStackTrace();
- }
- }
- }
- {
- System.out.println("Testing param dupes:");
- List<Parameter> rg = lastserv.getRunnerConfig()
- .getParameters();
- for (Parameter o : rg)
- {
- try
- {
- Parameter cpy = jalview.ws.jws2.ParameterUtils
- .copyParameter(o);
- } catch (Exception e)
- {
- System.err.println("Failed to copy " + o.getName());
- e.printStackTrace();
- } catch (Error e)
- {
- System.err.println("Failed to copy " + o.getName());
- e.printStackTrace();
- }
- }
- }
- {
- System.out.println("Testing param write:");
- List<String> writeparam = null, readparam = null;
- try
- {
- writeparam = jalview.ws.jws2.ParameterUtils
- .writeParameterSet(
- pr.getArguments(lastserv.getRunnerConfig()),
- " ");
- System.out.println("Testing param read :");
- List<Option> pset = jalview.ws.jws2.ParameterUtils
- .processParameters(writeparam,
- lastserv.getRunnerConfig(), " ");
- readparam = jalview.ws.jws2.ParameterUtils
- .writeParameterSet(pset, " ");
- Iterator<String> o = pr.getOptions().iterator(),
- s = writeparam.iterator(), t = readparam.iterator();
- boolean failed = false;
- while (s.hasNext() && t.hasNext())
- {
- String on = o.next(), sn = s.next(), st = t.next();
- if (!sn.equals(st))
- {
- System.out.println(
- "Original was " + on + " Phase 1 wrote " + sn
- + "\tPhase 2 wrote " + st);
- failed = true;
- }
- }
- if (failed)
- {
- System.out.println(
- "Original parameters:\n" + pr.getOptions());
- System.out.println(
- "Wrote parameters in first set:\n" + writeparam);
- System.out.println(
- "Wrote parameters in second set:\n" + readparam);
-
- }
- } catch (Exception e)
- {
- e.printStackTrace();
- }
- }
- WsJobParameters pgui = new WsJobParameters(lastserv,
- new JabaPreset(lastserv, pr));
- JFrame jf = new JFrame(MessageManager
- .formatMessage("label.ws_parameters_for", new String[]
- { lastserv.getActionText() }));
- JPanel cont = new JPanel(new BorderLayout());
- pgui.validate();
- cont.setPreferredSize(pgui.getPreferredSize());
- cont.add(pgui, BorderLayout.CENTER);
- jf.setLayout(new BorderLayout());
- jf.add(cont, BorderLayout.CENTER);
- jf.validate();
- final Thread thr = Thread.currentThread();
- jf.addWindowListener(new WindowListener()
- {
-
- @Override
- public void windowActivated(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowClosed(WindowEvent e)
- {
- }
-
- @Override
- public void windowClosing(WindowEvent e)
- {
- thr.interrupt();
-
- }
-
- @Override
- public void windowDeactivated(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowDeiconified(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowIconified(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- @Override
- public void windowOpened(WindowEvent e)
- {
- // TODO Auto-generated method stub
-
- }
-
- });
- jf.setVisible(true);
- boolean inter = false;
- while (!inter)
- {
- try
- {
- Thread.sleep(10000);
- } catch (Exception e)
- {
- inter = true;
- }
- }
- jf.dispose();
- }
- }
- }
- }
- }
-
public boolean isServiceDefaults()
{
return (!isModified()
--- /dev/null
+ package jalview.hmmer;
+
+ import jalview.analysis.SeqsetUtils;
+ import jalview.bin.Cache;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.AnnotatedCollectionI;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.HiddenMarkovModel;
+ import jalview.datamodel.SequenceGroup;
+ import jalview.datamodel.SequenceI;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.JvOptionPane;
+ import jalview.gui.Preferences;
+ import jalview.io.FastaFile;
+ import jalview.io.HMMFile;
+ import jalview.io.StockholmFile;
+ import jalview.util.FileUtils;
+ import jalview.util.MessageManager;
+ import jalview.util.Platform;
+ import jalview.ws.params.ArgumentI;
+
+ import java.io.BufferedReader;
+ import java.io.File;
+ import java.io.IOException;
+ import java.io.InputStreamReader;
+ import java.io.PrintWriter;
+ import java.nio.file.Paths;
+ import java.util.ArrayList;
+ import java.util.Hashtable;
+ import java.util.List;
+
+ /**
+ * Base class for hmmbuild, hmmalign and hmmsearch
+ *
+ * @author TZVanaalten
+ *
+ */
+ public abstract class HmmerCommand implements Runnable
+ {
+ public static final String HMMBUILD = "hmmbuild";
+
+ protected final AlignFrame af;
+
+ protected final AlignmentI alignment;
+
+ protected final List<ArgumentI> params;
+
+ /*
+ * constants for i18n lookup of passed parameter names
+ */
+ static final String DATABASE_KEY = "label.database";
+
+ static final String THIS_ALIGNMENT_KEY = "label.this_alignment";
+
+ static final String USE_ACCESSIONS_KEY = "label.use_accessions";
+
+ static final String AUTO_ALIGN_SEQS_KEY = "label.auto_align_seqs";
+
+ static final String NUMBER_OF_RESULTS_KEY = "label.number_of_results";
+
+ static final String NUMBER_OF_ITERATIONS = "label.number_of_iterations";
+
+ static final String TRIM_TERMINI_KEY = "label.trim_termini";
+
+ static final String RETURN_N_NEW_SEQ = "label.check_for_new_sequences";
+
+ static final String REPORTING_CUTOFF_KEY = "label.reporting_cutoff";
+
+ static final String CUTOFF_NONE = "label.default";
+
+ static final String CUTOFF_SCORE = "label.score";
+
+ static final String CUTOFF_EVALUE = "label.evalue";
+
+ static final String REPORTING_SEQ_EVALUE_KEY = "label.reporting_seq_evalue";
+
+ static final String REPORTING_DOM_EVALUE_KEY = "label.reporting_dom_evalue";
+
+ static final String REPORTING_SEQ_SCORE_KEY = "label.reporting_seq_score";
+
+ static final String REPORTING_DOM_SCORE_KEY = "label.reporting_dom_score";
+
+ static final String INCLUSION_SEQ_EVALUE_KEY = "label.inclusion_seq_evalue";
+
+ static final String INCLUSION_DOM_EVALUE_KEY = "label.inclusion_dom_evalue";
+
+ static final String INCLUSION_SEQ_SCORE_KEY = "label.inclusion_seq_score";
+
+ static final String INCLUSION_DOM_SCORE_KEY = "label.inclusion_dom_score";
+
+ static final String ARG_TRIM = "--trim";
+
+ static final String INCLUSION_THRESHOLD_KEY = "label.inclusion_threshold";
+
+ /**
+ * Constructor
+ *
+ * @param alignFrame
+ * @param args
+ */
+ public HmmerCommand(AlignFrame alignFrame, List<ArgumentI> args)
+ {
+ af = alignFrame;
+ alignment = af.getViewport().getAlignment();
+ params = args;
+ }
+
+ /**
+ * Answers true if preference HMMER_PATH is set, and its value is the path to
+ * a directory that contains an executable <code>hmmbuild</code> or
+ * <code>hmmbuild.exe</code>, else false
+ *
+ * @return
+ */
+ public static boolean isHmmerAvailable()
+ {
+ File exec = FileUtils.getExecutable(HMMBUILD,
+ Cache.getProperty(Preferences.HMMER_PATH));
+ return exec != null;
+ }
+
+ /**
+ * Uniquifies the sequences when exporting and stores their details in a
+ * hashtable
+ *
+ * @param seqs
+ */
+ protected Hashtable stashSequences(SequenceI[] seqs)
+ {
+ return SeqsetUtils.uniquify(seqs, true);
+ }
+
+ /**
+ * Restores the sequence data lost by uniquifying
+ *
+ * @param hashtable
+ * @param seqs
+ */
+ protected void recoverSequences(Hashtable hashtable, SequenceI[] seqs)
+ {
+ SeqsetUtils.deuniquify(hashtable, seqs);
+ }
+
+ /**
+ * Runs a command as a separate process and waits for it to complete. Answers
+ * true if the process return status is zero, else false.
+ *
+ * @param commands
+ * the executable command and any arguments to it
+ * @throws IOException
+ */
+ public boolean runCommand(List<String> commands)
+ throws IOException
+ {
- List<String> args = Platform.isWindows() ? wrapWithCygwin(commands)
++ List<String> args = Platform.isWindowsAndNotJS() ? wrapWithCygwin(commands)
+ : commands;
+
+ try
+ {
+ ProcessBuilder pb = new ProcessBuilder(args);
+ pb.redirectErrorStream(true); // merge syserr to sysout
- if (Platform.isWindows())
++ if (Platform.isWindowsAndNotJS())
+ {
+ String path = pb.environment().get("Path");
+ path = jalview.bin.Cache.getProperty("CYGWIN_PATH") + ";" + path;
+ pb.environment().put("Path", path);
+ }
+ final Process p = pb.start();
+ new Thread(new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ BufferedReader input = new BufferedReader(
+ new InputStreamReader(p.getInputStream()));
+ try
+ {
+ String line = input.readLine();
+ while (line != null)
+ {
+ System.out.println(line);
+ line = input.readLine();
+ }
+ } catch (IOException e)
+ {
+ e.printStackTrace();
+ }
+ }
+ }).start();
+
+ p.waitFor();
+ int exitValue = p.exitValue();
+ if (exitValue != 0)
+ {
+ Cache.log.error("Command failed, return code = " + exitValue);
+ Cache.log.error("Command/args were: " + args.toString());
+ }
+ return exitValue == 0; // 0 is success, by convention
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+ /**
+ * Converts the given command to a Cygwin "bash" command wrapper. The hmmer
+ * command and any arguments to it are converted into a single parameter to the
+ * bash command.
+ *
+ * @param commands
+ */
+ protected List<String> wrapWithCygwin(List<String> commands)
+ {
+ File bash = FileUtils.getExecutable("bash",
+ Cache.getProperty(Preferences.CYGWIN_PATH));
+ if (bash == null)
+ {
+ Cache.log.error("Cygwin shell not found");
+ return commands;
+ }
+
+ List<String> wrapped = new ArrayList<>();
+ // wrapped.add("C:\Users\tva\run");
+ wrapped.add(bash.getAbsolutePath());
+ wrapped.add("-c");
+
+ /*
+ * combine hmmbuild/search/align and arguments to a single string
+ */
+ StringBuilder sb = new StringBuilder();
+ for (String cmd : commands)
+ {
+ sb.append(" ").append(cmd);
+ }
+ wrapped.add(sb.toString());
+
+ return wrapped;
+ }
+
+ /**
+ * Exports an alignment, and reference (RF) annotation if present, to the
+ * specified file, in Stockholm format, removing all HMM sequences
+ *
+ * @param seqs
+ * @param toFile
+ * @param annotated
+ * @throws IOException
+ */
+ public void exportStockholm(SequenceI[] seqs, File toFile,
+ AnnotatedCollectionI annotated)
+ throws IOException
+ {
+ if (seqs == null)
+ {
+ return;
+ }
+ AlignmentI newAl = new Alignment(seqs);
+
+ if (!newAl.isAligned())
+ {
+ newAl.padGaps();
+ }
+
+ if (toFile != null && annotated != null)
+ {
+ AlignmentAnnotation[] annots = annotated.getAlignmentAnnotation();
+ if (annots != null)
+ {
+ for (AlignmentAnnotation annot : annots)
+ {
+ if (annot.label.contains("Reference") || "RF".equals(annot.label))
+ {
+ AlignmentAnnotation newRF;
+ if (annot.annotations.length > newAl.getWidth())
+ {
+ Annotation[] rfAnnots = new Annotation[newAl.getWidth()];
+ System.arraycopy(annot.annotations, 0, rfAnnots, 0,
+ rfAnnots.length);
+ newRF = new AlignmentAnnotation("RF", "Reference Positions",
+ rfAnnots);
+ }
+ else
+ {
+ newRF = new AlignmentAnnotation(annot);
+ }
+ newAl.addAnnotation(newRF);
+ }
+ }
+ }
+ }
+
+ for (SequenceI seq : newAl.getSequencesArray())
+ {
+ if (seq.getAnnotation() != null)
+ {
+ for (AlignmentAnnotation ann : seq.getAnnotation())
+ {
+ seq.removeAlignmentAnnotation(ann);
+ }
+ }
+ }
+
+ StockholmFile file = new StockholmFile(newAl);
+ String output = file.print(seqs, false);
+ PrintWriter writer = new PrintWriter(toFile);
+ writer.println(output);
+ writer.close();
+ }
+
+ /**
+ * Answers the full path to the given hmmer executable, or null if file cannot
+ * be found or is not executable
+ *
+ * @param cmd
+ * command short name e.g. hmmalign
+ * @return
+ * @throws IOException
+ */
+ protected String getCommandPath(String cmd)
+ throws IOException
+ {
+ String binariesFolder = Cache.getProperty(Preferences.HMMER_PATH);
+ // ensure any symlink to the directory is resolved:
+ binariesFolder = Paths.get(binariesFolder).toRealPath().toString();
+ File file = FileUtils.getExecutable(cmd, binariesFolder);
+ if (file == null && af != null)
+ {
+ JvOptionPane.showInternalMessageDialog(af, MessageManager
+ .formatMessage("label.executable_not_found", cmd));
+ }
+
+ return file == null ? null : getFilePath(file, true);
+ }
+
+ /**
+ * Exports an HMM to the specified file
+ *
+ * @param hmm
+ * @param hmmFile
+ * @throws IOException
+ */
+ public void exportHmm(HiddenMarkovModel hmm, File hmmFile)
+ throws IOException
+ {
+ if (hmm != null)
+ {
+ HMMFile file = new HMMFile(hmm);
+ PrintWriter writer = new PrintWriter(hmmFile);
+ writer.print(file.print());
+ writer.close();
+ }
+ }
+
+ // TODO is needed?
+ /**
+ * Exports a sequence to the specified file
+ *
+ * @param hmm
+ * @param hmmFile
+ * @throws IOException
+ */
+ public void exportSequence(SequenceI seq, File seqFile) throws IOException
+ {
+ if (seq != null)
+ {
+ FastaFile file = new FastaFile();
+ PrintWriter writer = new PrintWriter(seqFile);
+ writer.print(file.print(new SequenceI[] { seq }, false));
+ writer.close();
+ }
+ }
+
+ /**
+ * Answers the HMM profile for the profile sequence the user selected (default
+ * is just the first HMM sequence in the alignment)
+ *
+ * @return
+ */
+ protected HiddenMarkovModel getHmmProfile()
+ {
+ String alignToParamName = MessageManager.getString("label.use_hmm");
+ for (ArgumentI arg : params)
+ {
+ String name = arg.getName();
+ if (name.equals(alignToParamName))
+ {
+ String seqName = arg.getValue();
+ SequenceI hmmSeq = alignment.findName(seqName);
+ if (hmmSeq.hasHMMProfile())
+ {
+ return hmmSeq.getHMM();
+ }
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Answers the query sequence the user selected (default is just the first
+ * sequence in the alignment)
+ *
+ * @return
+ */
+ protected SequenceI getSequence()
+ {
+ String alignToParamName = MessageManager
+ .getString("label.use_sequence");
+ for (ArgumentI arg : params)
+ {
+ String name = arg.getName();
+ if (name.equals(alignToParamName))
+ {
+ String seqName = arg.getValue();
+ SequenceI seq = alignment.findName(seqName);
+ return seq;
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Answers an absolute path to the given file, in a format suitable for
+ * processing by a hmmer command. On a Windows platform, the native Windows file
+ * path is converted to Cygwin format, by replacing '\'with '/' and drive letter
+ * X with /cygdrive/x.
+ *
+ * @param resultFile
+ * @param isInCygwin
+ * True if file is to be read/written from within the Cygwin
+ * shell. Should be false for any imports.
+ * @return
+ */
+ protected String getFilePath(File resultFile, boolean isInCygwin)
+ {
+ String path = resultFile.getAbsolutePath();
- if (Platform.isWindows() && isInCygwin)
++ if (Platform.isWindowsAndNotJS() && isInCygwin)
+ {
+ // the first backslash escapes '\' for the regular expression argument
+ path = path.replaceAll("\\" + File.separator, "/");
+ int colon = path.indexOf(':');
+ if (colon > 0)
+ {
+ String drive = path.substring(0, colon);
+ path = path.replaceAll(drive + ":", "/cygdrive/" + drive);
+ }
+ }
+
+ return path;
+ }
+
+ /**
+ * A helper method that deletes any HMM consensus sequence from the given
+ * collection, and from the parent alignment if <code>ac</code> is a subgroup
+ *
+ * @param ac
+ */
+ void deleteHmmSequences(AnnotatedCollectionI ac)
+ {
+ List<SequenceI> hmmSeqs = ac.getHmmSequences();
+ for (SequenceI hmmSeq : hmmSeqs)
+ {
+ if (ac instanceof SequenceGroup)
+ {
+ ((SequenceGroup) ac).deleteSequence(hmmSeq, false);
+ AnnotatedCollectionI context = ac.getContext();
+ if (context != null && context instanceof AlignmentI)
+ {
+ ((AlignmentI) context).deleteSequence(hmmSeq);
+ }
+ }
+ else
+ {
+ ((AlignmentI) ac).deleteSequence(hmmSeq);
+ }
+ }
+ }
+
+ /**
+ * Sets the names of any duplicates within the given sequences to include their
+ * respective lengths. Deletes any duplicates that have the same name after this
+ * step
+ *
+ * @param seqs
+ */
+ void renameDuplicates(AlignmentI al)
+ {
+
+ SequenceI[] seqs = al.getSequencesArray();
+ List<Boolean> wasRenamed = new ArrayList<>();
+
+ for (SequenceI seq : seqs)
+ {
+ wasRenamed.add(false);
+ }
+
+ for (int i = 0; i < seqs.length; i++)
+ {
+ for (int j = 0; j < seqs.length; j++)
+ {
+ if (seqs[i].getName().equals(seqs[j].getName()) && i != j
+ && !wasRenamed.get(j))
+ {
+
+ wasRenamed.set(i, true);
+ String range = "/" + seqs[j].getStart() + "-" + seqs[j].getEnd();
+ // setting sequence name to include range - to differentiate between
+ // sequences of the same name. Currently have to include the range twice
+ // because the range is removed (once) when setting the name
+ // TODO come up with a better way of doing this
+ seqs[j].setName(seqs[j].getName() + range + range);
+ }
+
+ }
+ if (wasRenamed.get(i))
+ {
+ String range = "/" + seqs[i].getStart() + "-" + seqs[i].getEnd();
+ seqs[i].setName(seqs[i].getName() + range + range);
+ }
+ }
+
+ for (int i = 0; i < seqs.length; i++)
+ {
+ for (int j = 0; j < seqs.length; j++)
+ {
+ if (seqs[i].getName().equals(seqs[j].getName()) && i != j)
+ {
+ al.deleteSequence(j);
+ }
+ }
+ }
+ }
+
+ }
*/
package jalview.io;
-import jalview.api.AlignExportSettingI;
+import jalview.api.AlignExportSettingsI;
import jalview.api.AlignmentViewPanel;
- import jalview.api.FeatureSettingsModelI;
- import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
public interface AlignmentFileWriterI
@Override
public boolean isIdentifiable()
{
- return false;
+ return true;
}
+ },
+ HMMER3("HMMER3", "hmm", true, true)
+ {
+ @Override
+ public AlignmentFileReaderI getReader(FileParse source)
+ throws IOException
+ {
+ return new HMMFile(source);
+ }
+
+ @Override
+ public AlignmentFileWriterI getWriter(AlignmentI al)
+ {
+ return new HMMFile();
+ }
};
+
private boolean writable;
private boolean readable;
import jalview.util.MessageManager;
import jalview.ws.utils.UrlDownloadClient;
-import java.io.File;
-import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.List;
-import java.util.StringTokenizer;
-
-import javax.swing.SwingUtilities;
+
public class FileLoader implements Runnable
{
+ private static final String TAB = "\t";
+
String file;
DataSourceType protocol;
return alignFrame;
}
- public void updateRecentlyOpened()
+ public void LoadFileOntoAlignmentWaitTillLoaded(AlignViewport viewport,
+ String file, DataSourceType sourceType, FileFormatI format)
{
+ Vector<String> recent = new Vector<>();
+ if (protocol == DataSourceType.PASTE)
+ this.viewport = viewport;
+ this.file = file;
+ this.protocol = sourceType;
+ this.format = format;
- _LoadAlignmentFileWaitTillLoaded();
++ _LoadFileWaitTillLoaded();
+ }
+
- protected void _LoadAlignmentFileWaitTillLoaded()
- {
- Thread loader = new Thread(this);
- loader.start();
-
- while (loader.isAlive())
- {
- try
- {
- Thread.sleep(500);
- } catch (Exception ex)
- {
- }
- }
- }
+
+ /**
+ * Updates (or creates) the tab-separated list of recently opened files held
+ * under the given property name by inserting the filePath at the front of the
+ * list. Duplicates are removed, and the list is limited to 11 entries. The
+ * method returns the updated value of the property.
+ *
+ * @param filePath
+ * @param sourceType
+ */
+ public static String updateRecentlyOpened(String filePath,
+ DataSourceType sourceType)
+ {
+ if (sourceType != DataSourceType.FILE
+ && sourceType != DataSourceType.URL)
{
- // do nothing if the file was pasted in as text... there is no filename to
- // refer to it as.
- return;
+ return null;
}
- if (file != null
- && file.indexOf(System.getProperty("java.io.tmpdir")) > -1)
+
+ String propertyName = sourceType == DataSourceType.FILE ? "RECENT_FILE"
+ : "RECENT_URL";
+ String historyItems = Cache.getProperty(propertyName);
+ if (filePath != null
+ && filePath.indexOf(System.getProperty("java.io.tmpdir")) > -1)
{
// ignore files loaded from the system's temporary directory
- return;
+ return null;
}
- String type = protocol == DataSourceType.FILE ? "RECENT_FILE"
- : "RECENT_URL";
- String historyItems = Cache.getProperty(type);
-
- StringTokenizer st;
+ List<String> recent = new ArrayList<>();
if (historyItems != null)
{
while (st.hasMoreTokens())
{
- recent.addElement(st.nextToken().trim());
+ String trimmed = st.nextToken().trim();
- if (!recent.contains(trimmed))
- {
- recent.add(trimmed);
- }
++ recent.add(trimmed);
}
}
--- /dev/null
+ package jalview.io;
+
-import jalview.api.AlignExportSettingI;
++import jalview.api.AlignExportSettingsI;
+ import jalview.api.AlignmentViewPanel;
+ import jalview.datamodel.HMMNode;
+ import jalview.datamodel.HiddenMarkovModel;
+ import jalview.datamodel.SequenceI;
+
+ import java.io.BufferedReader;
+ import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.List;
+ import java.util.Scanner;
+
+
+ /**
+ * Adds capability to read in and write out HMMER3 files. .
+ *
+ *
+ * @author TZVanaalten
+ *
+ */
+ public class HMMFile extends AlignFile
+ implements AlignmentFileReaderI, AlignmentFileWriterI
+ {
+ private static final String TERMINATOR = "//";
+
+ /*
+ * keys to data in HMM file, used to store as properties of the HiddenMarkovModel
+ */
+ public static final String HMM = "HMM";
+
+ public static final String NAME = "NAME";
+
+ public static final String ACCESSION_NUMBER = "ACC";
+
+ public static final String DESCRIPTION = "DESC";
+
+ public static final String LENGTH = "LENG";
+
+ public static final String MAX_LENGTH = "MAXL";
+
+ public static final String ALPHABET = "ALPH";
+
+ public static final String DATE = "DATE";
+
+ public static final String COMMAND_LOG = "COM";
+
+ public static final String NUMBER_OF_SEQUENCES = "NSEQ";
+
+ public static final String EFF_NUMBER_OF_SEQUENCES = "EFFN";
+
+ public static final String CHECK_SUM = "CKSUM";
+
+ public static final String STATISTICS = "STATS";
+
+ public static final String COMPO = "COMPO";
+
+ public static final String GATHERING_THRESHOLD = "GA";
+
+ public static final String TRUSTED_CUTOFF = "TC";
+
+ public static final String NOISE_CUTOFF = "NC";
+
+ public static final String VITERBI = "VITERBI";
+
+ public static final String MSV = "MSV";
+
+ public static final String FORWARD = "FORWARD";
+
+ public static final String MAP = "MAP";
+
+ public static final String REFERENCE_ANNOTATION = "RF";
+
+ public static final String CONSENSUS_RESIDUE = "CONS";
+
+ public static final String CONSENSUS_STRUCTURE = "CS";
+
+ public static final String MASKED_VALUE = "MM";
+
+ private static final String ALPH_AMINO = "amino";
+
+ private static final String ALPH_DNA = "DNA";
+
+ private static final String ALPH_RNA = "RNA";
+
+ private static final String ALPHABET_AMINO = "ACDEFGHIKLMNPQRSTVWY";
+
+ private static final String ALPHABET_DNA = "ACGT";
+
+ private static final String ALPHABET_RNA = "ACGU";
+
+ private static final int NUMBER_OF_TRANSITIONS = 7;
+
+ private static final String SPACE = " ";
+
+ /*
+ * optional guide line added to an output HMMER file, purely for readability
+ */
+ private static final String TRANSITIONTYPELINE = " m->m m->i m->d i->m i->i d->m d->d";
+
+ private static String NL = System.lineSeparator();
+
+ private HiddenMarkovModel hmm;
+
+ // number of symbols in the alphabet used in the hidden Markov model
+ private int numberOfSymbols;
+
+ /**
+ * Constructor that parses immediately
+ *
+ * @param inFile
+ * @param type
+ * @throws IOException
+ */
+ public HMMFile(String inFile, DataSourceType type) throws IOException
+ {
+ super(inFile, type);
+ }
+
+ /**
+ * Constructor that parses immediately
+ *
+ * @param source
+ * @throws IOException
+ */
+ public HMMFile(FileParse source) throws IOException
+ {
+ super(source);
+ }
+
+ /**
+ * Default constructor
+ */
+ public HMMFile()
+ {
+ }
+
+ /**
+ * Constructor for HMMFile used for exporting
+ *
+ * @param hmm
+ */
+ public HMMFile(HiddenMarkovModel markov)
+ {
+ hmm = markov;
+ }
+
+ /**
+ * Returns the HMM produced by parsing a HMMER3 file
+ *
+ * @return
+ */
+ public HiddenMarkovModel getHMM()
+ {
+ return hmm;
+ }
+
+ /**
+ * Gets the name of the hidden Markov model
+ *
+ * @return
+ */
+ public String getName()
+ {
+ return hmm.getName();
+ }
+
+ /**
+ * Reads the data from HMM file into the HMM model
+ */
+ @Override
+ public void parse()
+ {
+ try
+ {
+ hmm = new HiddenMarkovModel();
+ parseHeaderLines(dataIn);
+ parseModel(dataIn);
+ } catch (Exception e)
+ {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Reads the header properties from a HMMER3 file and saves them in the
+ * HiddeMarkovModel. This method exits after reading the next line after the
+ * HMM line.
+ *
+ * @param input
+ * @throws IOException
+ */
+ void parseHeaderLines(BufferedReader input) throws IOException
+ {
+ boolean readingHeaders = true;
+ hmm.setFileHeader(input.readLine());
+ String line = input.readLine();
+ while (readingHeaders && line != null)
+ {
+ Scanner parser = new Scanner(line);
+ String next = parser.next();
+ if (ALPHABET.equals(next))
+ {
+ String alphabetType = parser.next();
+ hmm.setProperty(ALPHABET, alphabetType);
+ String alphabet = ALPH_DNA.equalsIgnoreCase(alphabetType)
+ ? ALPHABET_DNA
+ : (ALPH_RNA.equalsIgnoreCase(alphabetType) ? ALPHABET_RNA
+ : ALPHABET_AMINO);
+ numberOfSymbols = hmm.setAlphabet(alphabet);
+ }
+ else if (HMM.equals(next))
+ {
+ readingHeaders = false;
+ String symbols = line.substring(line.indexOf(HMM) + HMM.length());
+ numberOfSymbols = hmm.setAlphabet(symbols);
+ }
+ else if (STATISTICS.equals(next))
+ {
+ parser.next();
+ String key;
+ String value;
+ key = parser.next();
+ value = parser.next() + SPACE + SPACE + parser.next();
+ hmm.setProperty(key, value);
+ }
+ else
+ {
+ String key = next;
+ String value = parser.next();
+ while (parser.hasNext())
+ {
+ value = value + SPACE + parser.next();
+ }
+ hmm.setProperty(key, value);
+ }
+ parser.close();
+ line = input.readLine();
+ }
+ }
+
+ /**
+ * Parses the model data from the HMMER3 file. The input buffer should be
+ * positioned at the (optional) COMPO line if there is one, else at the insert
+ * emissions line for the BEGIN node of the model.
+ *
+ * @param input
+ * @throws IOException
+ */
+ void parseModel(BufferedReader input) throws IOException
+ {
+ /*
+ * specification says there must always be an HMM header (already read)
+ * and one more header (guide headings) which is skipped here
+ */
+ int nodeNo = 0;
+ String line = input.readLine();
+ List<HMMNode> nodes = new ArrayList<>();
+
+ while (line != null && !TERMINATOR.equals(line))
+ {
+ HMMNode node = new HMMNode();
+ nodes.add(node);
+ Scanner scanner = new Scanner(line);
+ String next = scanner.next();
+
+ /*
+ * expect COMPO (optional) for average match emissions
+ * or a node number followed by node's match emissions
+ */
+ if (COMPO.equals(next) || nodeNo > 0)
+ {
+ /*
+ * parse match emissions
+ */
+ double[] matches = parseDoubles(scanner, numberOfSymbols);
+ node.setMatchEmissions(matches);
+ if (!COMPO.equals(next))
+ {
+ int resNo = parseAnnotations(scanner, node);
+ if (resNo == 0)
+ {
+ /*
+ * no MAP annotation provided, just number off from 0 (begin node)
+ */
+ resNo = nodeNo;
+ }
+ node.setResidueNumber(resNo);
+ }
+ line = input.readLine();
+ }
+ scanner.close();
+
+ /*
+ * parse insert emissions
+ */
+ scanner = new Scanner(line);
+ double[] inserts = parseDoubles(scanner, numberOfSymbols);
+ node.setInsertEmissions(inserts);
+ scanner.close();
+
+ /*
+ * parse state transitions
+ */
+ line = input.readLine();
+ scanner = new Scanner(line);
+ double[] transitions = parseDoubles(scanner,
+ NUMBER_OF_TRANSITIONS);
+ node.setStateTransitions(transitions);
+ scanner.close();
+ line = input.readLine();
+
+ nodeNo++;
+ }
+
+ hmm.setNodes(nodes);
+ }
+
+ /**
+ * Parses the annotations on the match emission line and add them to the node.
+ * (See p109 of the HMMER User Guide (V3.1b2) for the specification.) Returns
+ * the residue position that the node maps to, if provided, else zero.
+ *
+ * @param scanner
+ * @param node
+ */
+ int parseAnnotations(Scanner scanner, HMMNode node)
+ {
+ int mapTo = 0;
+
+ /*
+ * map from hmm node to sequence position, if provided
+ */
+ if (scanner.hasNext())
+ {
+ String value = scanner.next();
+ if (!"-".equals(value))
+ {
+ try
+ {
+ mapTo = Integer.parseInt(value);
+ node.setResidueNumber(mapTo);
+ } catch (NumberFormatException e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ /*
+ * hmm consensus residue if provided, else '-'
+ */
+ if (scanner.hasNext())
+ {
+ node.setConsensusResidue(scanner.next().charAt(0));
+ }
+
+ /*
+ * RF reference annotation, if provided, else '-'
+ */
+ if (scanner.hasNext())
+ {
+ node.setReferenceAnnotation(scanner.next().charAt(0));
+ }
+
+ /*
+ * 'm' for masked position, if provided, else '-'
+ */
+ if (scanner.hasNext())
+ {
+ node.setMaskValue(scanner.next().charAt(0));
+ }
+
+ /*
+ * structure consensus symbol, if provided, else '-'
+ */
+ if (scanner.hasNext())
+ {
+ node.setConsensusStructure(scanner.next().charAt(0));
+ }
+
+ return mapTo;
+ }
+
+ /**
+ * Fills an array of doubles parsed from an input line
+ *
+ * @param input
+ * @param numberOfElements
+ * @return
+ * @throws IOException
+ */
+ static double[] parseDoubles(Scanner input,
+ int numberOfElements) throws IOException
+ {
+ double[] values = new double[numberOfElements];
+ for (int i = 0; i < numberOfElements; i++)
+ {
+ if (!input.hasNext())
+ {
+ throw new IOException("Incomplete data");
+ }
+ String next = input.next();
+ if (next.contains("*"))
+ {
+ values[i] = Double.NEGATIVE_INFINITY;
+ }
+ else
+ {
+ double prob = Double.valueOf(next);
+ prob = Math.pow(Math.E, -prob);
+ values[i] = prob;
+ }
+ }
+ return values;
+ }
+
+ /**
+ * Returns a string to be added to the StringBuilder containing the entire
+ * output String.
+ *
+ * @param initialColumnSeparation
+ * The initial whitespace separation between the left side of the
+ * file and first character.
+ * @param columnSeparation
+ * The separation between subsequent data entries.
+ * @param data
+ * The list of data to be added to the String.
+ * @return
+ */
+ String addData(int initialColumnSeparation,
+ int columnSeparation, List<String> data)
+ {
+ String line = "";
+ boolean first = true;
+ for (String value : data)
+ {
+ int sep = first ? initialColumnSeparation : columnSeparation;
+ line += String.format("%" + sep + "s", value);
+ first = false;
+ }
+ return line;
+ }
+
+ /**
+ * Converts list of characters into a list of Strings.
+ *
+ * @param list
+ * @return Returns the list of Strings.
+ */
+ List<String> charListToStringList(List<Character> list)
+ {
+ List<String> strList = new ArrayList<>();
+ for (char value : list)
+ {
+ String strValue = Character.toString(value);
+ strList.add(strValue);
+ }
+ return strList;
+ }
+
+ /**
+ * Converts an array of doubles into a list of Strings, rounded to the nearest
+ * 5th decimal place
+ *
+ * @param doubles
+ * @param noOfDecimals
+ * @return
+ */
+ List<String> doublesToStringList(double[] doubles)
+ {
+ List<String> strList = new ArrayList<>();
+ for (double value : doubles)
+ {
+ String strValue;
+ if (value > 0)
+ {
+ strValue = String.format("%.5f", value);
+ }
+ else if (value == -0.00000d)
+ {
+ strValue = "0.00000";
+ }
+ else
+ {
+ strValue = "*";
+ }
+ strList.add(strValue);
+ }
+ return strList;
+ }
+
+ /**
+ * Appends model data in string format to the string builder
+ *
+ * @param output
+ */
+ void appendModelAsString(StringBuilder output)
+ {
+ output.append(HMM).append(" ");
+ String charSymbols = hmm.getSymbols();
+ for (char c : charSymbols.toCharArray())
+ {
+ output.append(String.format("%9s", c));
+ }
+ output.append(NL).append(TRANSITIONTYPELINE);
+
+ int length = hmm.getLength();
+
+ for (int nodeNo = 0; nodeNo <= length; nodeNo++)
+ {
+ String matchLine = String.format("%7s",
+ nodeNo == 0 ? COMPO : Integer.toString(nodeNo));
+
+ double[] doubleMatches = convertToLogSpace(
+ hmm.getNode(nodeNo).getMatchEmissions());
+ List<String> strMatches = doublesToStringList(doubleMatches);
+ matchLine += addData(10, 9, strMatches);
+
+ if (nodeNo != 0)
+ {
+ matchLine += SPACE + (hmm.getNodeMapPosition(nodeNo));
+ matchLine += SPACE + hmm.getConsensusResidue(nodeNo);
+ matchLine += SPACE + hmm.getReferenceAnnotation(nodeNo);
+ if (hmm.getFileHeader().contains("HMMER3/f"))
+ {
+ matchLine += SPACE + hmm.getMaskedValue(nodeNo);
+ matchLine += SPACE + hmm.getConsensusStructure(nodeNo);
+ }
+ }
+
+ output.append(NL).append(matchLine);
+
+ String insertLine = "";
+
+ double[] doubleInserts = convertToLogSpace(
+ hmm.getNode(nodeNo).getInsertEmissions());
+ List<String> strInserts = doublesToStringList(doubleInserts);
+ insertLine += addData(17, 9, strInserts);
+
+ output.append(NL).append(insertLine);
+
+ String transitionLine = "";
+ double[] doubleTransitions = convertToLogSpace(
+ hmm.getNode(nodeNo).getStateTransitions());
+ List<String> strTransitions = doublesToStringList(
+ doubleTransitions);
+ transitionLine += addData(17, 9, strTransitions);
+
+ output.append(NL).append(transitionLine);
+ }
+ }
+
+ /**
+ * Appends formatted HMM file properties to the string builder
+ *
+ * @param output
+ */
+ void appendProperties(StringBuilder output)
+ {
+ output.append(hmm.getFileHeader());
+
+ String format = "%n%-5s %1s";
+ appendProperty(output, format, NAME);
+ appendProperty(output, format, ACCESSION_NUMBER);
+ appendProperty(output, format, DESCRIPTION);
+ appendProperty(output, format, LENGTH);
+ appendProperty(output, format, MAX_LENGTH);
+ appendProperty(output, format, ALPHABET);
+ appendBooleanProperty(output, format, REFERENCE_ANNOTATION);
+ appendBooleanProperty(output, format, MASKED_VALUE);
+ appendBooleanProperty(output, format, CONSENSUS_RESIDUE);
+ appendBooleanProperty(output, format, CONSENSUS_STRUCTURE);
+ appendBooleanProperty(output, format, MAP);
+ appendProperty(output, format, DATE);
+ appendProperty(output, format, NUMBER_OF_SEQUENCES);
+ appendProperty(output, format, EFF_NUMBER_OF_SEQUENCES);
+ appendProperty(output, format, CHECK_SUM);
+ appendProperty(output, format, GATHERING_THRESHOLD);
+ appendProperty(output, format, TRUSTED_CUTOFF);
+ appendProperty(output, format, NOISE_CUTOFF);
+
+ if (hmm.getMSV() != null)
+ {
+ format = "%n%-19s %18s";
+ output.append(String.format(format, "STATS LOCAL MSV", hmm.getMSV()));
+
+ output.append(String.format(format, "STATS LOCAL VITERBI",
+ hmm.getViterbi()));
+
+ output.append(String.format(format, "STATS LOCAL FORWARD",
+ hmm.getForward()));
+ }
+ }
+
+ /**
+ * Appends 'yes' or 'no' for the given property, according to whether or not
+ * it is set in the HMM
+ *
+ * @param output
+ * @param format
+ * @param propertyName
+ */
+ private void appendBooleanProperty(StringBuilder output, String format,
+ String propertyName)
+ {
+ boolean set = hmm.getBooleanProperty(propertyName);
+ output.append(String.format(format, propertyName,
+ set ? HiddenMarkovModel.YES : HiddenMarkovModel.NO));
+ }
+
+ /**
+ * Appends the value of the given property to the output, if not null
+ *
+ * @param output
+ * @param format
+ * @param propertyName
+ */
+ private void appendProperty(StringBuilder output, String format,
+ String propertyName)
+ {
+ String value = hmm.getProperty(propertyName);
+ if (value != null)
+ {
+ output.append(String.format(format, propertyName, value));
+ }
+ }
+
+ @Override
+ public String print(SequenceI[] sequences, boolean jvsuffix)
+ {
+ if (sequences[0].getHMM() != null)
+ {
+ hmm = sequences[0].getHMM();
+ }
+ return print();
+ }
+
+ /**
+ * Prints the .hmm file to a String.
+ *
+ * @return
+ */
+ public String print()
+ {
+ StringBuilder output = new StringBuilder();
+ appendProperties(output);
+ output.append(NL);
+ appendModelAsString(output);
+ output.append(NL).append(TERMINATOR).append(NL);
+ return output.toString();
+ }
+
+ /**
+ * Converts the probabilities contained in an array into log space
+ *
+ * @param ds
+ */
+ double[] convertToLogSpace(double[] ds)
+ {
+ double[] converted = new double[ds.length];
+ for (int i = 0; i < ds.length; i++)
+ {
+ double prob = ds[i];
+ double logProb = -1 * Math.log(prob);
+
+ converted[i] = logProb;
+ }
+ return converted;
+ }
+
+ /**
+ * Returns the HMM sequence produced by reading a .hmm file.
+ */
+ @Override
+ public SequenceI[] getSeqsAsArray()
+ {
+ SequenceI hmmSeq = hmm.getConsensusSequence();
+ SequenceI[] seq = new SequenceI[1];
+ seq[0] = hmmSeq;
+ return seq;
+ }
+
+ @Override
+ public void setNewlineString(String newLine)
+ {
+ NL = newLine;
+ }
+
+ @Override
- public void setExportSettings(AlignExportSettingI exportSettings)
++ public void setExportSettings(AlignExportSettingsI exportSettings)
+ {
+
+ }
+
+ @Override
+ public void configureForView(AlignmentViewPanel viewpanel)
+ {
+
+ }
+
+ @Override
+ public boolean hasWarningMessage()
+ {
+ return false;
+ }
+
+ @Override
+ public String getWarningMessage()
+ {
+ return "warning message";
+ }
+
+ }
+
{
private static final String ANNOTATION = "annotation";
- // private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
- //
- // private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
-
- public static final Regex DETECT_BRACKETS = new Regex(
- "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
-
+ private static final char UNDERSCORE = '_';
+
- private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
-
- private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
-
- // private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
- // private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
-
- public static final Regex DETECT_BRACKETS = new Regex(
- "(<|>|\\[|\\]|\\(|\\)|\\{|\\})");
-
// WUSS extended symbols. Avoid ambiguity with protein SS annotations by using NOT_RNASS first.
public static final String RNASS_BRACKETS = "<>[](){}AaBbCcDdEeFfGgHhIiJjKkLlMmNnOoPpQqRrSsTtUuVvWwXxYyZz";
if (alAnot != null)
{
Annotation[] ann;
- for (int j = 0, nj = alAnot.length; j < nj; j++)
-
+ for (int j = 0; j < alAnot.length; j++)
{
-
- String key = type2id(alAnot[j].label);
- boolean isrna = alAnot[j].isValidStruc();
-
- if (isrna)
- {
- // hardwire to secondary structure if there is RNA secondary
- // structure on the annotation
- key = "SS";
- }
- if (key == null)
+ if (alAnot[j].annotations != null)
{
+ String key = type2id(alAnot[j].label);
+ boolean isrna = alAnot[j].isValidStruc();
- continue;
- }
+ if (isrna)
+ {
+ // hardwire to secondary structure if there is RNA secondary
+ // structure on the annotation
+ key = "SS";
+ }
+ if (key == null)
+ {
-
+ continue;
+ }
- // out.append("#=GR ");
- out.append(new Format("%-" + maxid + "s").form(
- "#=GR " + printId(seq, jvSuffix) + " " + key + " "));
- ann = alAnot[j].annotations;
- String sseq = "";
- for (int k = 0, nk = ann.length; k < nk; k++)
- {
- sseq += outputCharacter(key, k, isrna, ann, seq);
- }
- out.append(sseq);
- out.append(newline);
+ // out.append("#=GR ");
+ out.append(new Format("%-" + maxid + "s").form(
+ "#=GR " + printId(s[i], jvSuffix) + " " + key + " "));
+ ann = alAnot[j].annotations;
- String seq = "";
++ String sseq = "";
+ for (int k = 0; k < ann.length; k++)
+ {
- seq += outputCharacter(key, k, isrna, ann, s[i]);
++ sseq += outputCharacter(key, k, isrna, ann, s[i]);
+ }
- out.append(seq);
++ out.append(sseq);
+ out.append(newline);
- }
++ }
}
+
}
out.append(new Format("%-" + maxid + "s")
*/
package jalview.io.packed;
--import jalview.api.FeatureColourI;
import jalview.datamodel.AlignmentI;
import jalview.io.AppletFormatAdapter;
import jalview.io.FileFormatI;
protected JMenuItem closeMenuItem = new JMenuItem();
- protected JMenu webService = new JMenu();
+ public JMenu webService = new JMenu();// BH 2019 was protected, but not
+ // sufficient for AlignFrame thread run
++ // JBP - followed suite for these other service related GUI elements.
++ // TODO: check we really need these to be public
++ public JMenu hmmerMenu = new JMenu();
- public JMenuItem webServiceNoServices;// BH 2019 was protected, but not
- // sufficient for AlignFrame thread run
- protected JMenu hmmerMenu = new JMenu();
-
- protected JMenuItem webServiceNoServices;
++ public JMenuItem webServiceNoServices;
protected JCheckBoxMenuItem viewBoxesMenuItem = new JCheckBoxMenuItem();
protected JCheckBoxMenuItem normaliseSequenceLogo = new JCheckBoxMenuItem();
+ protected JCheckBoxMenuItem showInformationHistogram = new JCheckBoxMenuItem();
+
+ protected JCheckBoxMenuItem showHMMSequenceLogo = new JCheckBoxMenuItem();
+
+ protected JCheckBoxMenuItem normaliseHMMSequenceLogo = new JCheckBoxMenuItem();
+
protected JCheckBoxMenuItem applyAutoAnnotationSettings = new JCheckBoxMenuItem();
+ protected JMenuItem openFeatureSettings;
+
private SequenceAnnotationOrder annotationSortOrder;
private boolean showAutoCalculatedAbove = false;
@Override
public void actionPerformed(ActionEvent e)
{
- saveAs_actionPerformed(e);
+ saveAs_actionPerformed();
}
};
-
+
// FIXME getDefaultToolkit throws an exception in Headless mode
KeyStroke keyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_S,
jalview.util.ShortcutKeyMaskExWrapper.getMenuShortcutKeyMaskEx()
@Override
public void actionPerformed(ActionEvent e)
{
- delete_actionPerformed(e);
+ delete_actionPerformed();
}
});
-
+
pasteMenu.setText(MessageManager.getString("action.paste"));
JMenuItem pasteNew = new JMenuItem(
MessageManager.getString("label.to_new_alignment"));
@Override
public void actionPerformed(ActionEvent e)
{
- fetchSequence_actionPerformed(e);
+ fetchSequence_actionPerformed();
}
});
-
+
JMenuItem associatedData = new JMenuItem(
MessageManager.getString("label.load_features_annotations"));
associatedData.addActionListener(new ActionListener()
@Override
public void actionPerformed(ActionEvent e)
{
- associatedData_actionPerformed(e);
+ try
+ {
+ associatedData_actionPerformed(e);
+ } catch (IOException | InterruptedException e1)
+ {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
}
});
- loadVcf = new JMenuItem(MessageManager.getString("label.load_vcf_file"));
+ loadVcf = new JMenuItem(
+ MessageManager.getString("label.load_vcf_file"));
loadVcf.setToolTipText(MessageManager.getString("label.load_vcf"));
loadVcf.addActionListener(new ActionListener()
{
alignFrameMenuBar.add(formatMenu);
alignFrameMenuBar.add(colourMenu);
alignFrameMenuBar.add(calculateMenu);
- alignFrameMenuBar.add(webService);
- alignFrameMenuBar.add(hmmerMenu);
+ if (!Platform.isJS())
+ {
+ alignFrameMenuBar.add(webService);
++ alignFrameMenuBar.add(hmmerMenu);
+ }
-
+
fileMenu.add(fetchSequence);
fileMenu.add(addSequenceMenu);
fileMenu.add(reload);
fileMenu.add(exportAnnotations);
fileMenu.add(loadTreeMenuItem);
fileMenu.add(associatedData);
- fileMenu.add(loadVcf);
+ if (!Platform.isJS())
+ {
+ fileMenu.add(loadVcf);
+ }
fileMenu.addSeparator();
fileMenu.add(closeMenuItem);
-
+
pasteMenu.add(pasteNew);
pasteMenu.add(pasteThis);
editMenu.add(undoMenuItem);
calculateMenu.addSeparator();
calculateMenu.add(expandAlignment);
calculateMenu.add(extractScores);
- calculateMenu.addSeparator();
- calculateMenu.add(runGroovy);
-
+ if (!Platform.isJS())
+ {
+ calculateMenu.addSeparator();
+ calculateMenu.add(runGroovy);
+ }
-
webServiceNoServices = new JMenuItem(
MessageManager.getString("label.no_services"));
webService.add(webServiceNoServices);
}
/**
- * Initialises the Output tab
+ * Initialises the hmmer tabbed panel
+ *
+ * @return
+ */
+ private JPanel initHMMERTab()
+ {
+ hmmerTab = new JPanel();
+ hmmerTab.setLayout(new BoxLayout(hmmerTab, BoxLayout.Y_AXIS));
+ hmmerTab.setLayout(new MigLayout("flowy"));
+
+ /*
+ * path to hmmer binaries folder
+ */
+ JPanel installationPanel = new JPanel(new MigLayout("flowy"));
+ // new FlowLayout(FlowLayout.LEFT));
+ JvSwingUtils.createTitledBorder(installationPanel,
+ MessageManager.getString("label.installation"), true);
+ hmmerTab.add(installationPanel);
+ JLabel hmmerLocation = new JLabel(
+ MessageManager.getString("label.hmmer_location"));
+ hmmerLocation.setFont(LABEL_FONT);
+ final int pathFieldLength = 40;
+ hmmerPath = new JTextField(pathFieldLength);
+ hmmerPath.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ if (e.getClickCount() == 2)
+ {
+ String chosen = openFileChooser(true);
+ if (chosen != null)
+ {
+ hmmerPath.setText(chosen);
+ validateHmmerPath();
+ }
+ }
+ }
+ });
+ installationPanel.add(hmmerLocation);
+ installationPanel.add(hmmerPath);
+
+ /*
+ * path to Cygwin binaries folder (for Windows)
+ */
- if (Platform.isWindows())
++ if (Platform.isWindowsAndNotJS())
+ {
+ JLabel cygwinLocation = new JLabel(
+ MessageManager.getString("label.cygwin_location"));
+ cygwinLocation.setFont(LABEL_FONT);
+ cygwinPath = new JTextField(pathFieldLength);
+ cygwinPath.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseClicked(MouseEvent e)
+ {
+ if (e.getClickCount() == 2)
+ {
+ String chosen = openFileChooser(true);
+ if (chosen != null)
+ {
+ cygwinPath.setText(chosen);
+ validateCygwinPath();
+ }
+ }
+ }
+ });
+ installationPanel.add(cygwinLocation);
+ installationPanel.add(cygwinPath);
+ }
+
+ /*
+ * preferences for hmmalign
+ */
+ JPanel alignOptionsPanel = new JPanel(new MigLayout());
+ // new FlowLayout(FlowLayout.LEFT));
+ JvSwingUtils.createTitledBorder(alignOptionsPanel,
+ MessageManager.getString("label.hmmalign_options"), true);
+ hmmerTab.add(alignOptionsPanel);
+ hmmrTrimTermini = new JCheckBox();
+ hmmrTrimTermini.setFont(LABEL_FONT);
+ hmmrTrimTermini.setText(MessageManager.getString("label.trim_termini"));
+ alignOptionsPanel.add(hmmrTrimTermini);
+
+ /*
+ * preferences for hmmsearch
+ */
+ JPanel searchOptions = new JPanel(new MigLayout());
+ // FlowLayout(FlowLayout.LEFT));
+ JvSwingUtils.createTitledBorder(searchOptions,
+ MessageManager.getString("label.hmmsearch_options"), true);
+ hmmerTab.add(searchOptions);
+ JLabel sequencesToKeep = new JLabel(
+ MessageManager.getString("label.no_of_sequences"));
+ sequencesToKeep.setFont(LABEL_FONT);
+ searchOptions.add(sequencesToKeep);
+ hmmerSequenceCount = new JTextField(5);
+ searchOptions.add(hmmerSequenceCount);
+
+ /*
+ * preferences for Information Content annotation
+ */
+ // JPanel dummy = new JPanel(new FlowLayout(FlowLayout.LEFT));
+ JPanel annotationOptions = new JPanel(new MigLayout("left"));
+ JvSwingUtils.createTitledBorder(annotationOptions,
+ MessageManager.getString("label.information_annotation"), true);
+ // dummy.add(annotationOptions);
+ hmmerTab.add(annotationOptions);
+ ButtonGroup backgroundOptions = new ButtonGroup();
+ hmmerBackgroundUniprot = new JRadioButton(
+ MessageManager.getString("label.freq_uniprot"));
+ hmmerBackgroundUniprot.setFont(LABEL_FONT);
+ hmmerBackgroundAlignment = new JRadioButton(
+ MessageManager.getString("label.freq_alignment"));
+ hmmerBackgroundAlignment.setFont(LABEL_FONT);
+ backgroundOptions.add(hmmerBackgroundUniprot);
+ backgroundOptions.add(hmmerBackgroundAlignment);
+ backgroundOptions.setSelected(hmmerBackgroundUniprot.getModel(), true);
+ // disable buttons for now as annotation only uses Uniprot background
+ hmmerBackgroundAlignment.setEnabled(false);
+ hmmerBackgroundUniprot.setEnabled(false);
+ annotationOptions.add(hmmerBackgroundUniprot, "wrap");
+ annotationOptions.add(hmmerBackgroundAlignment);
+
+ return hmmerTab;
+ }
+
+ /**
+ * Initialises the Output tabbed panel.
*
* @return
*/
private FontMetrics fm;
- private final boolean MAC = Platform.isAMac();
+ private final boolean USE_FILL_ROUND_RECT = Platform.isAMacAndNotJS();
- boolean av_renderHistogram = true, av_renderProfile = true,
- av_normaliseProfile = false;
+ // todo remove these flags, read from group/viewport where needed
+ boolean av_renderHistogram = true;
+
+ boolean av_renderProfile = true;
+
+ boolean av_normaliseProfile = false;
+
+ boolean av_infoHeight = false;
ResidueShaderI profcolour = null;
for (int i = 0; i < aa.length; i++)
{
AlignmentAnnotation row = aa[i];
- isRNA = row.isRNA();
+ boolean renderHistogram = true;
+ boolean renderProfile = false;
+ boolean normaliseProfile = false;
+ boolean isRNA = row.isRNA();
+
+ // check if this is a consensus annotation row and set the display
+ // settings appropriately
+ // TODO: generalise this to have render styles for consensus/profile
+ // data
+ if (row.groupRef != null && row == row.groupRef.getConsensus())
{
- // check if this is a consensus annotation row and set the display
- // settings appropriately
- // TODO: generalise this to have render styles for consensus/profile
- // data
- if (row.groupRef != null && row == row.groupRef.getConsensus())
- {
- renderHistogram = row.groupRef.isShowConsensusHistogram();
- renderProfile = row.groupRef.isShowSequenceLogo();
- normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
- }
- else if (row == consensusAnnot || row == structConsensusAnnot
- || row == complementConsensusAnnot)
- {
- renderHistogram = av_renderHistogram;
- renderProfile = av_renderProfile;
- normaliseProfile = av_normaliseProfile;
- }
- else if (InformationThread.HMM_CALC_ID.equals(row.getCalcId()))
+ renderHistogram = row.groupRef.isShowConsensusHistogram();
+ renderProfile = row.groupRef.isShowSequenceLogo();
+ normaliseProfile = row.groupRef.isNormaliseSequenceLogo();
+ }
+ else if (row == consensusAnnot || row == structConsensusAnnot
+ || row == complementConsensusAnnot)
+ {
+ renderHistogram = av_renderHistogram;
+ renderProfile = av_renderProfile;
+ normaliseProfile = av_normaliseProfile;
+ }
++ else if (InformationThread.HMM_CALC_ID.equals(row.getCalcId()))
++ {
++ if (row.groupRef != null)
+ {
- if (row.groupRef != null)
- {
- renderHistogram = row.groupRef.isShowInformationHistogram();
- renderProfile = row.groupRef.isShowHMMSequenceLogo();
- normaliseProfile = row.groupRef.isNormaliseHMMSequenceLogo();
- }
- else
- {
- renderHistogram = av.isShowInformationHistogram();
- renderProfile = av.isShowHMMSequenceLogo();
- normaliseProfile = av.isNormaliseHMMSequenceLogo();
- }
++ renderHistogram = row.groupRef.isShowInformationHistogram();
++ renderProfile = row.groupRef.isShowHMMSequenceLogo();
++ normaliseProfile = row.groupRef.isNormaliseHMMSequenceLogo();
+ }
+ else
+ {
- renderHistogram = true;
- // don't need to set render/normaliseProfile since they are not
- // currently used in any other annotation track renderer
++ renderHistogram = av.isShowInformationHistogram();
++ renderProfile = av.isShowHMMSequenceLogo();
++ normaliseProfile = av.isNormaliseHMMSequenceLogo();
+ }
+ }
++ else if (row == consensusAnnot || row == structConsensusAnnot
++ || row == complementConsensusAnnot)
++ {
++ renderHistogram = av_renderHistogram;
++ renderProfile = av_renderProfile;
++ normaliseProfile = av_normaliseProfile;
++ }
+
Annotation[] row_annotations = row.annotations;
if (!row.visible)
{
* results of secondary structure base pair consensus for visible portion of
* view
*/
- protected Hashtable[] hStrucConsensus = null;
+ protected Hashtable<String, Object>[] hStrucConsensus = null;
protected Conservation hconservation = null;
-
+
@Override
public void setConservation(Conservation cons)
{
}
@Override
+ public void setHmmProfiles(ProfilesI info)
+ {
+ hmmProfiles = info;
+ }
+
+ @Override
+ public ProfilesI getHmmProfiles()
+ {
+ return hmmProfiles;
+ }
+
+ @Override
- public Hashtable[] getComplementConsensusHash()
+ public Hashtable<String, Object>[] getComplementConsensusHash()
{
return hcomplementConsensus;
}
}
@Override
+ public AlignmentExportData getAlignExportData(AlignExportSettingsI options)
+ {
+ AlignmentI alignmentToExport = null;
+ String[] omitHidden = null;
+ alignmentToExport = null;
+
+ if (hasHiddenColumns() && !options.isExportHiddenColumns())
+ {
+ omitHidden = getViewAsString(false,
+ options.isExportHiddenSequences());
+ }
+
+ int[] alignmentStartEnd = new int[2];
+ if (hasHiddenRows() && options.isExportHiddenSequences())
+ {
+ alignmentToExport = getAlignment().getHiddenSequences()
+ .getFullAlignment();
+ }
+ else
+ {
+ alignmentToExport = getAlignment();
+ }
+ alignmentStartEnd = getAlignment().getHiddenColumns()
+ .getVisibleStartAndEndIndex(alignmentToExport.getWidth());
+ AlignmentExportData ed = new AlignmentExportData(alignmentToExport,
+ omitHidden, alignmentStartEnd);
+ return ed;
+ }
+
++ @Override
+ public boolean isNormaliseSequenceLogo()
+ {
+ return normaliseSequenceLogo;
+ }
+
+ public void setNormaliseSequenceLogo(boolean state)
+ {
+ normaliseSequenceLogo = state;
+ }
+
+ @Override
+ public boolean isNormaliseHMMSequenceLogo()
+ {
+ return hmmNormaliseSequenceLogo;
+ }
+
+ public void setNormaliseHMMSequenceLogo(boolean state)
+ {
+ hmmNormaliseSequenceLogo = state;
+ }
-
/**
* flag set to indicate if structure views might be out of sync with sequences
* in the alignment
import jalview.xml.binding.embl.EntryType;
import jalview.xml.binding.embl.EntryType.Feature;
import jalview.xml.binding.embl.EntryType.Feature.Qualifier;
- import jalview.xml.binding.jalview.JalviewModel;
+import jalview.xml.binding.embl.ROOT;
import jalview.xml.binding.embl.XrefType;
import java.io.File;
@Override
public List<WsParamSetI> getPresets()
{
- List<WsParamSetI> prefs = new ArrayList();
+ List<WsParamSetI> prefs = new ArrayList<>();
if (servicePresets == null)
{
- servicePresets = new Hashtable<String, JabaPreset>();
+ servicePresets = new Hashtable<>();
PresetManager prman;
if ((prman = service.getPresets()) != null)
{
private void populateWSMenuEntry(JMenu jws2al,
final AlignFrame alignFrame, String typeFilter)
{
- if (running || services == null || services.size() == 0)
- {
- return;
- }
-
- /**
- * eventually, JWS2 services will appear under the same align/etc submenus.
- * for moment we keep them separate.
- */
- JMenu atpoint;
- List<Jws2Instance> enumerableServices = new ArrayList<>();
- // jws2al.removeAll();
- Map<String, Jws2Instance> preferredHosts = new HashMap<>();
- Map<String, List<Jws2Instance>> alternates = new HashMap<>();
- for (Jws2Instance service : services.toArray(new Jws2Instance[0]))
- {
- if (!isRecalculable(service.action))
- {
- // add 'one shot' services to be displayed using the classic menu
- // structure
- enumerableServices.add(service);
- }
- else
- {
- if (!preferredHosts.containsKey(service.serviceType))
- {
- Jws2Instance preferredInstance = getPreferredServiceFor(
- alignFrame, service.serviceType);
- if (preferredInstance != null)
- {
- preferredHosts.put(service.serviceType, preferredInstance);
- }
- else
- {
- preferredHosts.put(service.serviceType, service);
- }
- }
- List<Jws2Instance> ph = alternates.get(service.serviceType);
- if (preferredHosts.get(service.serviceType) != service)
- {
- if (ph == null)
- {
- ph = new ArrayList<>();
- }
- ph.add(service);
- alternates.put(service.serviceType, ph);
- }
- }
-
- }
-
- // create GUI element for classic services
- addEnumeratedServices(jws2al, alignFrame, enumerableServices);
- // and the instantaneous services
- for (final Jws2Instance service : preferredHosts.values())
- {
- atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
- JMenuItem hitm;
- if (atpoint.getItemCount() > 1)
- {
- // previous service of this type already present
- atpoint.addSeparator();
- }
- atpoint.add(hitm = new JMenuItem(service.getHost()));
- hitm.setForeground(Color.blue);
- hitm.addActionListener(new ActionListener()
- {
-
- @Override
- public void actionPerformed(ActionEvent e)
- {
- Desktop.showUrl(service.getHost());
- }
- });
- hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
- MessageManager.getString("label.open_jabaws_web_page")));
-
- service.attachWSMenuEntry(atpoint, alignFrame);
- if (alternates.containsKey(service.serviceType))
- {
- atpoint.add(hitm = new JMenu(
- MessageManager.getString("label.switch_server")));
- hitm.setToolTipText(JvSwingUtils.wrapTooltip(false,
- MessageManager.getString("label.choose_jabaws_server")));
- for (final Jws2Instance sv : alternates.get(service.serviceType))
- {
- JMenuItem itm;
- hitm.add(itm = new JMenuItem(sv.getHost()));
- itm.setForeground(Color.blue);
- itm.addActionListener(new ActionListener()
- {
-
- @Override
- public void actionPerformed(ActionEvent arg0)
- {
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- setPreferredServiceFor(alignFrame, sv.serviceType,
- sv.action, sv);
- changeSupport.firePropertyChange("services",
- new Vector<Jws2Instance>(), services);
- }
- }).start();
-
- }
- });
- }
- }
- }
- }
-
- /**
- * add services using the Java 2.5/2.6/2.7 system which optionally creates
- * submenus to index by host and service program type
- */
- private void addEnumeratedServices(final JMenu jws2al,
- final AlignFrame alignFrame,
- List<Jws2Instance> enumerableServices)
- {
- boolean byhost = Cache.getDefault("WSMENU_BYHOST", false),
- bytype = Cache.getDefault("WSMENU_BYTYPE", false);
- /**
- * eventually, JWS2 services will appear under the same align/etc submenus.
- * for moment we keep them separate.
- */
- JMenu atpoint;
-
- List<String> hostLabels = new ArrayList<>();
- Hashtable<String, String> lasthostFor = new Hashtable<>();
- Hashtable<String, ArrayList<Jws2Instance>> hosts = new Hashtable<>();
- ArrayList<String> hostlist = new ArrayList<>();
- for (Jws2Instance service : enumerableServices)
- {
- ArrayList<Jws2Instance> hostservices = hosts.get(service.getHost());
- if (hostservices == null)
- {
- hosts.put(service.getHost(),
- hostservices = new ArrayList<>());
- hostlist.add(service.getHost());
- }
- hostservices.add(service);
- }
- // now add hosts in order of the given array
- for (String host : hostlist)
- {
- Jws2Instance orderedsvcs[] = hosts.get(host)
- .toArray(new Jws2Instance[1]);
- String sortbytype[] = new String[orderedsvcs.length];
- for (int i = 0; i < sortbytype.length; i++)
- {
- sortbytype[i] = orderedsvcs[i].serviceType;
- }
- jalview.util.QuickSort.sort(sortbytype, orderedsvcs);
- for (final Jws2Instance service : orderedsvcs)
- {
- atpoint = JvSwingUtils.findOrCreateMenu(jws2al, service.action);
- String type = service.serviceType;
- if (byhost)
- {
- atpoint = JvSwingUtils.findOrCreateMenu(atpoint, host);
- if (atpoint.getToolTipText() == null)
- {
- atpoint.setToolTipText(MessageManager
- .formatMessage("label.services_at", new String[]
- { host }));
- }
- }
- if (bytype)
- {
- atpoint = JvSwingUtils.findOrCreateMenu(atpoint, type);
- if (atpoint.getToolTipText() == null)
- {
- atpoint.setToolTipText(service.getActionText());
- }
- }
- if (!byhost && !hostLabels.contains(
- host + service.serviceType + service.getActionText()))
- // !hostLabels.contains(host + (bytype ?
- // service.serviceType+service.getActionText() : "")))
- {
- // add a marker indicating where this service is hosted
- // relies on services from the same host being listed in a
- // contiguous
- // group
- JMenuItem hitm;
- if (hostLabels.contains(host))
- {
- atpoint.addSeparator();
- }
- else
- {
- hostLabels.add(host);
- }
- if (lasthostFor.get(service.action) == null
- || !lasthostFor.get(service.action).equals(host))
- {
- atpoint.add(hitm = new JMenuItem(host));
- hitm.setForeground(Color.blue);
- hitm.addActionListener(new ActionListener()
- {
-
- @Override
- public void actionPerformed(ActionEvent e)
- {
- Desktop.showUrl(service.getHost());
- }
- });
- hitm.setToolTipText(
- JvSwingUtils.wrapTooltip(true, MessageManager
- .getString("label.open_jabaws_web_page")));
- lasthostFor.put(service.action, host);
- }
- hostLabels.add(
- host + service.serviceType + service.getActionText());
- }
-
- service.attachWSMenuEntry(atpoint, alignFrame);
- }
- }
+ PreferredServiceRegistry.getRegistry().populateWSMenuEntry(
+ getServices(),
+ changeSupport, jws2al,
+ alignFrame, typeFilter);
}
+ /**
+ *
+ * @param args
+ * @j2sIgnore
+ */
public static void main(String[] args)
{
if (args.length > 0)
--- /dev/null
+ /*
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
+ *
+ * This file is part of Jalview.
+ *
+ * Jalview 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 3
+ * of the License, or (at your option) any later version.
+ *
+ * Jalview 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 Jalview. If not, see <http://www.gnu.org/licenses/>.
+ * The Jalview Authors are detailed in the 'AUTHORS' file.
+ */
+ package jalview.ws.jws2;
+
+ import jalview.analysis.AlignSeq;
+ import jalview.analysis.AlignmentAnnotationUtils;
+ import jalview.analysis.SeqsetUtils;
+ import jalview.api.AlignViewportI;
+ import jalview.api.AlignmentViewPanel;
+ import jalview.api.FeatureColourI;
+ import jalview.bin.Cache;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.AnnotatedCollectionI;
+ import jalview.datamodel.Annotation;
+ import jalview.datamodel.ContiguousI;
+ import jalview.datamodel.Mapping;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.features.FeatureMatcherSetI;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.Desktop;
+ import jalview.gui.IProgressIndicator;
+ import jalview.gui.IProgressIndicatorHandler;
+ import jalview.gui.JvOptionPane;
+ import jalview.gui.WebserviceInfo;
+ import jalview.schemes.FeatureSettingsAdapter;
+ import jalview.schemes.ResidueProperties;
+ import jalview.util.MapList;
+ import jalview.util.MessageManager;
+ import jalview.workers.AlignCalcWorker;
+ import jalview.ws.JobStateSummary;
+ import jalview.ws.api.CancellableI;
+ import jalview.ws.api.JalviewServiceEndpointProviderI;
+ import jalview.ws.api.JobId;
+ import jalview.ws.api.SequenceAnnotationServiceI;
+ import jalview.ws.api.ServiceWithParameters;
+ import jalview.ws.api.WSAnnotationCalcManagerI;
+ import jalview.ws.gui.AnnotationWsJob;
+ import jalview.ws.jws2.dm.AAConSettings;
+ import jalview.ws.params.ArgumentI;
+ import jalview.ws.params.WsParamSetI;
+
+ import java.util.ArrayList;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+
+ public class SeqAnnotationServiceCalcWorker extends AlignCalcWorker
+ implements WSAnnotationCalcManagerI
+ {
+
+ protected ServiceWithParameters service;
+
+ protected WsParamSetI preset;
+
+ protected List<ArgumentI> arguments;
+
+ protected IProgressIndicator guiProgress;
+
+ protected boolean submitGaps = true;
+
+ /**
+ * by default, we filter out non-standard residues before submission
+ */
+ protected boolean filterNonStandardResidues = true;
+
+ /**
+ * Recover any existing parameters for this service
+ */
+ protected void initViewportParams()
+ {
+ if (getCalcId() != null)
+ {
+ ((jalview.gui.AlignViewport) alignViewport).setCalcIdSettingsFor(
+ getCalcId(),
+ new AAConSettings(true, service, this.preset, arguments),
+ true);
+ }
+ }
+
+ /**
+ *
+ * @return null or a string used to recover all annotation generated by this
+ * worker
+ */
+ public String getCalcId()
+ {
+ return service.getAlignAnalysisUI() == null ? null
+ : service.getAlignAnalysisUI().getCalcId();
+ }
+
+ public WsParamSetI getPreset()
+ {
+ return preset;
+ }
+
+ public List<ArgumentI> getArguments()
+ {
+ return arguments;
+ }
+
+ /**
+ * reconfigure and restart the AAConClient. This method will spawn a new
+ * thread that will wait until any current jobs are finished, modify the
+ * parameters and restart the conservation calculation with the new values.
+ *
+ * @param newpreset
+ * @param newarguments
+ */
+ public void updateParameters(final WsParamSetI newpreset,
+ final List<ArgumentI> newarguments)
+ {
+ preset = newpreset;
+ arguments = newarguments;
+ calcMan.startWorker(this);
+ initViewportParams();
+ }
+ protected boolean alignedSeqs = true;
+
+ protected boolean nucleotidesAllowed = false;
+
+ protected boolean proteinAllowed = false;
+
+ /**
+ * record sequences for mapping result back to afterwards
+ */
+ protected boolean bySequence = false;
+
+ protected Map<String, SequenceI> seqNames;
+
+ // TODO: convert to bitset
+ protected boolean[] gapMap;
+
+ int realw;
+
+ protected int start;
+
+ int end;
+
+ private AlignFrame alignFrame;
+
+ public boolean[] getGapMap()
+ {
+ return gapMap;
+ }
+
+ public SeqAnnotationServiceCalcWorker(AlignViewportI alignViewport,
+ AlignmentViewPanel alignPanel)
+ {
+ super(alignViewport, alignPanel);
+ }
+
+ public SeqAnnotationServiceCalcWorker(ServiceWithParameters service,
+ AlignFrame alignFrame,
+ WsParamSetI preset, List<ArgumentI> paramset)
+ {
+ this(alignFrame.getCurrentView(), alignFrame.alignPanel);
+ // TODO: both these fields needed ?
+ this.alignFrame = alignFrame;
+ this.guiProgress = alignFrame;
+ this.preset = preset;
+ this.arguments = paramset;
+ this.service = service;
+ try
+ {
+ annotService = (jalview.ws.api.SequenceAnnotationServiceI) ((JalviewServiceEndpointProviderI) service)
+ .getEndpoint();
+ } catch (ClassCastException cce)
+ {
+ JvOptionPane.showMessageDialog(Desktop.desktop,
+ MessageManager.formatMessage(
+ "label.service_called_is_not_an_annotation_service",
+ new String[]
+ { service.getName() }),
+ MessageManager.getString("label.internal_jalview_error"),
+ JvOptionPane.WARNING_MESSAGE);
+
+ }
+ // configure submission flags
+ proteinAllowed = service.isProteinService();
+ nucleotidesAllowed = service.isNucleotideService();
+ alignedSeqs = service.isNeedsAlignedSequences();
+ bySequence = !service.isAlignmentAnalysis();
+ filterNonStandardResidues = service.isFilterSymbols();
+ min_valid_seqs = service.getMinimumInputSequences();
+ submitGaps = service.isAlignmentAnalysis();
+
+ if (service.isInteractiveUpdate())
+ {
+ initViewportParams();
+ }
+ }
+
+ /**
+ *
+ * @return true if the submission thread should attempt to submit data
+ */
+ public boolean hasService()
+ {
+ return annotService != null;
+ }
+
+ protected jalview.ws.api.SequenceAnnotationServiceI annotService = null;
+
+ volatile JobId rslt = null;
+
+ AnnotationWsJob running = null;
+
+ private int min_valid_seqs;
+
+ @Override
+ public void run()
+ {
+ if (checkDone())
+ {
+ return;
+ }
+ if (!hasService())
+ {
+ calcMan.workerComplete(this);
+ return;
+ }
+
+ long progressId = -1;
+
+ int serverErrorsLeft = 3;
+ final boolean cancellable = CancellableI.class
+ .isAssignableFrom(annotService.getClass());
+ StringBuffer msg = new StringBuffer();
+ JobStateSummary job = new JobStateSummary();
+ WebserviceInfo info = new WebserviceInfo("foo", "bar", false);
+ try
+ {
+ List<SequenceI> seqs = getInputSequences(
+ alignViewport.getAlignment(),
+ bySequence ? alignViewport.getSelectionGroup() : null);
+
+ if (seqs == null || !checkValidInputSeqs(seqs))
+ {
+ jalview.bin.Cache.log.debug(
+ "Sequences for analysis service were null or not valid");
+ calcMan.workerComplete(this);
+ return;
+ }
+
+ if (guiProgress != null)
+ {
+ guiProgress.setProgressBar(service.getActionText(),
+ progressId = System.currentTimeMillis());
+ }
+ jalview.bin.Cache.log.debug("submitted " + seqs.size()
+ + " sequences to " + service.getActionText());
+
+ rslt = annotService.submitToService(seqs, getPreset(),
+ getArguments());
+ if (rslt == null)
+ {
+ return;
+ }
+ // TODO: handle job submission error reporting here.
+ Cache.log.debug("Service " + service.getUri() + "\nSubmitted job ID: "
+ + rslt);
+ ;
+ // ///
+ // otherwise, construct WsJob and any UI handlers
+ running = new AnnotationWsJob();
+ running.setJobHandle(rslt);
+ running.setSeqNames(seqNames);
+ running.setStartPos(start);
+ running.setSeqs(seqs);
+ job.updateJobPanelState(info, "", running);
+ if (guiProgress != null)
+ {
+ guiProgress.registerHandler(progressId,
+ new IProgressIndicatorHandler()
+ {
+
+ @Override
+ public boolean cancelActivity(long id)
+ {
+ ((CancellableI) annotService).cancel(running);
+ return true;
+ }
+
+ @Override
+ public boolean canCancel()
+ {
+ return cancellable;
+ }
+ });
+ }
+
+ // ///
+ // and poll for updates until job finishes, fails or becomes stale
+
+ boolean finished = false;
+ do
+ {
+ Cache.log.debug("Updating status for annotation service.");
+ annotService.updateStatus(running);
+ job.updateJobPanelState(info, "", running);
+ if (running.isSubjobComplete())
+ {
+ Cache.log.debug(
+ "Finished polling analysis service job: status reported is "
+ + running.getState());
+ finished = true;
+ }
+ else
+ {
+ Cache.log.debug("Status now " + running.getState());
+ }
+
+ if (calcMan.isPending(this) && isInteractiveUpdate())
+ {
+ Cache.log.debug("Analysis service job is stale. aborting.");
+ // job has become stale.
+ if (!finished) {
+ finished = true;
+ // cancel this job and yield to the new job
+ try
+ {
+ if (cancellable
+ && ((CancellableI) annotService).cancel(running))
+ {
+ System.err.println("Cancelled job: " + rslt);
+ }
+ else
+ {
+ System.err.println("FAILED TO CANCEL job: " + rslt);
+ }
+
+ } catch (Exception x)
+ {
+
+ }
+ }
+ rslt = running.getJobHandle();
+ return;
+ }
+
+ // pull any stats - some services need to flush log output before
+ // results are available
+ Cache.log.debug("Updating progress log for annotation service.");
+
+ try
+ {
+ annotService.updateJobProgress(running);
+ } catch (Throwable thr)
+ {
+ Cache.log.debug("Ignoring exception during progress update.",
+ thr);
+ }
+ Cache.log.trace("Result of poll: " + running.getStatus());
+
+ if (!finished && !running.isFailed())
+ {
+ try
+ {
+ Cache.log.debug("Analysis service job thread sleeping.");
+ Thread.sleep(200);
+ Cache.log.debug("Analysis service job thread woke.");
+ } catch (InterruptedException x)
+ {
+ }
+ ;
+ }
+ } while (!finished);
+
+ Cache.log.debug("Job poll loop exited. Job is " + running.getState());
+ // TODO: need to poll/retry
+ if (serverErrorsLeft > 0)
+ {
+ try
+ {
+ Thread.sleep(200);
+ } catch (InterruptedException x)
+ {
+ }
+ }
+ if (running.isFinished())
+ {
+ // expect there to be results to collect
+ // configure job with the associated view's feature renderer, if one
+ // exists.
+ // TODO: here one would also grab the 'master feature renderer' in order
+ // to enable/disable
+ // features automatically according to user preferences
+ running.setFeatureRenderer(
+ ((jalview.gui.AlignmentPanel) ap).cloneFeatureRenderer());
+ Cache.log.debug("retrieving job results.");
+ final Map<String, FeatureColourI> featureColours = new HashMap<>();
+ final Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
+ List<AlignmentAnnotation> returnedAnnot = annotService
+ .getAnnotationResult(running.getJobHandle(), seqs,
+ featureColours, featureFilters);
+
+ Cache.log.debug("Obtained " + (returnedAnnot == null ? "no rows"
+ : ("" + returnedAnnot.size())));
+ Cache.log.debug("There were " + featureColours.size()
+ + " feature colours and " + featureFilters.size()
+ + " filters defined.");
+
+ // TODO
+ // copy over each annotation row reurned and also defined on each
+ // sequence, excluding regions not annotated due to gapMap/column
+ // visibility
+
+ // update calcId if it is not already set on returned annotation
+ if (returnedAnnot != null)
+ {
+ for (AlignmentAnnotation aa : returnedAnnot)
+ {
+ // assume that any CalcIds already set
+ if (getCalcId() != null && aa.getCalcId() == null
+ || "".equals(aa.getCalcId()))
+ {
+ aa.setCalcId(getCalcId());
+ }
+ // autocalculated annotation are created by interactive alignment
+ // analysis services
+ aa.autoCalculated = service.isAlignmentAnalysis()
+ && service.isInteractiveUpdate();
+ }
+ }
+
+ running.setAnnotation(returnedAnnot);
+
+ if (running.hasResults())
+ {
+ jalview.bin.Cache.log.debug("Updating result annotation from Job "
+ + rslt + " at " + service.getUri());
+ updateResultAnnotation(true);
+ if (running.isTransferSequenceFeatures())
+ {
+ // TODO
+ // look at each sequence and lift over any features, excluding
+ // regions
+ // not annotated due to gapMap/column visibility
+
+ jalview.bin.Cache.log.debug(
+ "Updating feature display settings and transferring features from Job "
+ + rslt + " at " + service.getUri());
+ // TODO: consider merge rather than apply here
+ alignViewport.applyFeaturesStyle(new FeatureSettingsAdapter()
+ {
+ @Override
+ public FeatureColourI getFeatureColour(String type)
+ {
+ return featureColours.get(type);
+ }
+
+ @Override
+ public FeatureMatcherSetI getFeatureFilters(String type)
+ {
+ return featureFilters.get(type);
+ }
+
+ @Override
+ public boolean isFeatureDisplayed(String type)
+ {
+ return featureColours.containsKey(type);
+ }
+
+ });
+ // TODO: JAL-1150 - create sequence feature settings API for
+ // defining
+ // styles and enabling/disabling feature overlay on alignment panel
+
+ if (alignFrame.alignPanel == ap)
+ {
+ alignViewport.setShowSequenceFeatures(true);
+ alignFrame.setMenusForViewport();
+ }
+ }
+ ap.adjustAnnotationHeight();
+ }
+ }
+ Cache.log.debug("Annotation Service Worker thread finished.");
+ }
+ // TODO: use service specitic exception handlers
+ // catch (JobSubmissionException x)
+ // {
+ //
+ // System.err.println(
+ // "submission error with " + getServiceActionText() + " :");
+ // x.printStackTrace();
+ // calcMan.disableWorker(this);
+ // } catch (ResultNotAvailableException x)
+ // {
+ // System.err.println("collection error:\nJob ID: " + rslt);
+ // x.printStackTrace();
+ // calcMan.disableWorker(this);
+ //
+ // } catch (OutOfMemoryError error)
+ // {
+ // calcMan.disableWorker(this);
+ //
+ // ap.raiseOOMWarning(getServiceActionText(), error);
+ // }
+ catch (Throwable x)
+ {
+ calcMan.disableWorker(this);
+
+ System.err
+ .println("Blacklisting worker due to unexpected exception:");
+ x.printStackTrace();
+ } finally
+ {
+
+ calcMan.workerComplete(this);
+ if (ap != null)
+ {
+ if (guiProgress != null && progressId != -1)
+ {
+ guiProgress.setProgressBar("", progressId);
+ }
+ // TODO: may not need to paintAlignment again !
+ ap.paintAlignment(false, false);
+ }
+ if (msg.length() > 0)
+ {
+ // TODO: stash message somewhere in annotation or alignment view.
+ // code below shows result in a text box popup
+ /*
+ * jalview.gui.CutAndPasteTransfer cap = new
+ * jalview.gui.CutAndPasteTransfer(); cap.setText(msg.toString());
+ * jalview.gui.Desktop.addInternalFrame(cap,
+ * "Job Status for "+getServiceActionText(), 600, 400);
+ */
+ }
+ }
+
+ }
+
+ /**
+ * validate input for dynamic/non-dynamic update context TODO: move to
+ * analysis interface ?
+ * @param seqs
+ *
+ * @return true if input is valid
+ */
+ boolean checkValidInputSeqs(List<SequenceI> seqs)
+ {
+ int nvalid = 0;
+ for (SequenceI sq : seqs)
+ {
+ if (sq.getStart() <= sq.getEnd()
+ && (sq.isProtein() ? proteinAllowed : nucleotidesAllowed))
+ {
+ if (submitGaps
+ || sq.getLength() == (sq.getEnd() - sq.getStart() + 1))
+ {
+ nvalid++;
+ }
+ }
+ }
+ return nvalid >= min_valid_seqs;
+ }
+
+ public void cancelCurrentJob()
+ {
+ try
+ {
+ String id = running.getJobId();
+ if (((CancellableI) annotService).cancel(running))
+ {
+ System.err.println("Cancelled job " + id);
+ }
+ else
+ {
+ System.err.println("Job " + id + " couldn't be cancelled.");
+ }
+ } catch (Exception q)
+ {
+ q.printStackTrace();
+ }
+ }
+
+ /**
+ * Interactive updating. Analysis calculations that work on the currently
+ * displayed alignment data should cancel existing jobs when the input data
+ * has changed.
+ *
+ * @return true if a running job should be cancelled because new input data is
+ * available for analysis
+ */
+ boolean isInteractiveUpdate()
+ {
+ return service.isInteractiveUpdate();
+ }
+
+ /**
+ * decide what sequences will be analysed TODO: refactor to generate
+ * List<SequenceI> for submission to service interface
+ *
+ * @param alignment
+ * @param inputSeqs
+ * @return
+ */
+ public List<SequenceI> getInputSequences(AlignmentI alignment,
+ AnnotatedCollectionI inputSeqs)
+ {
+ if (alignment == null || alignment.getWidth() <= 0
+ || alignment.getSequences() == null || alignment.isNucleotide()
+ ? !nucleotidesAllowed
+ : !proteinAllowed)
+ {
+ return null;
+ }
+ if (inputSeqs == null || inputSeqs.getWidth() <= 0
+ || inputSeqs.getSequences() == null
+ || inputSeqs.getSequences().size() < 1)
+ {
+ inputSeqs = alignment;
+ }
+
+ List<SequenceI> seqs = new ArrayList<>();
+
+ int minlen = 10;
+ int ln = -1;
+ if (bySequence)
+ {
+ seqNames = new HashMap<>();
+ }
+ gapMap = new boolean[0];
+ start = inputSeqs.getStartRes();
+ end = inputSeqs.getEndRes();
+ // TODO: URGENT! unify with JPred / MSA code to handle hidden regions
+ // correctly
+ // TODO: push attributes into WsJob instance (so they can be safely
+ // persisted/restored
+ for (SequenceI sq : (inputSeqs.getSequences()))
+ {
+ if (bySequence
+ ? sq.findPosition(end + 1)
+ - sq.findPosition(start + 1) > minlen - 1
+ : sq.getEnd() - sq.getStart() > minlen - 1)
+ {
+ String newname = SeqsetUtils.unique_name(seqs.size() + 1);
+ // make new input sequence with or without gaps
+ if (seqNames != null)
+ {
+ seqNames.put(newname, sq);
+ }
+ SequenceI seq;
+ if (submitGaps)
+ {
+ seqs.add(seq = new jalview.datamodel.Sequence(newname,
+ sq.getSequenceAsString()));
+ if (gapMap == null || gapMap.length < seq.getLength())
+ {
+ boolean[] tg = gapMap;
+ gapMap = new boolean[seq.getLength()];
+ System.arraycopy(tg, 0, gapMap, 0, tg.length);
+ for (int p = tg.length; p < gapMap.length; p++)
+ {
+ gapMap[p] = false; // init as a gap
+ }
+ }
+ for (int apos : sq.gapMap())
+ {
+ char sqc = sq.getCharAt(apos);
+ if (!filterNonStandardResidues
+ || (sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
+ : ResidueProperties.nucleotideIndex[sqc] < 5))
+ {
+ gapMap[apos] = true; // aligned and real amino acid residue
+ }
+ ;
+ }
+ }
+ else
+ {
+ // TODO: add ability to exclude hidden regions
+ seqs.add(seq = new jalview.datamodel.Sequence(newname,
+ AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
+ sq.getSequenceAsString(start, end + 1))));
+ // for annotation need to also record map to sequence start/end
+ // position in range
+ // then transfer back to original sequence on return.
+ }
+ if (seq.getLength() > ln)
+ {
+ ln = seq.getLength();
+ }
+ }
+ }
+ if (alignedSeqs && submitGaps)
+ {
+ realw = 0;
+ for (int i = 0; i < gapMap.length; i++)
+ {
+ if (gapMap[i])
+ {
+ realw++;
+ }
+ }
+ // try real hard to return something submittable
+ // TODO: some of AAcon measures need a minimum of two or three amino
+ // acids at each position, and AAcon doesn't gracefully degrade.
+ for (int p = 0; p < seqs.size(); p++)
+ {
+ SequenceI sq = seqs.get(p);
+ // strip gapped columns
+ char[] padded = new char[realw],
+ orig = sq.getSequence();
+ for (int i = 0, pp = 0; i < realw; pp++)
+ {
+ if (gapMap[pp])
+ {
+ if (orig.length > pp)
+ {
+ padded[i++] = orig[pp];
+ }
+ else
+ {
+ padded[i++] = '-';
+ }
+ }
+ }
+ seqs.set(p, new jalview.datamodel.Sequence(sq.getName(),
+ new String(padded)));
+ }
+ }
+ return seqs;
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+ updateResultAnnotation(false);
+ }
+
+ public void updateResultAnnotation(boolean immediate)
+ {
+ if ((immediate || !calcMan.isWorking(this)) && running != null
+ && running.hasResults())
+ {
+ List<AlignmentAnnotation> ourAnnot = running.getAnnotation(),
+ newAnnots = new ArrayList<>();
+ //
+ // update graphGroup for all annotation
+ //
+ /**
+ * find a graphGroup greater than any existing ones this could be a method
+ * provided by alignment Alignment.getNewGraphGroup() - returns next
+ * unused graph group
+ */
+ int graphGroup = 1;
+ if (alignViewport.getAlignment().getAlignmentAnnotation() != null)
+ {
+ for (AlignmentAnnotation ala : alignViewport.getAlignment()
+ .getAlignmentAnnotation())
+ {
+ if (ala.graphGroup > graphGroup)
+ {
+ graphGroup = ala.graphGroup;
+ }
+ }
+ }
+ /**
+ * update graphGroup in the annotation rows returned from service
+ */
+ // TODO: look at sequence annotation rows and update graph groups in the
+ // case of reference annotation.
+ for (AlignmentAnnotation ala : ourAnnot)
+ {
+ if (ala.graphGroup > 0)
+ {
+ ala.graphGroup += graphGroup;
+ }
+ SequenceI aseq = null;
+
+ /**
+ * transfer sequence refs and adjust gapmap
+ */
+ if (ala.sequenceRef != null)
+ {
+ SequenceI seq = running.getSeqNames()
+ .get(ala.sequenceRef.getName());
+ aseq = seq;
+ while (seq.getDatasetSequence() != null)
+ {
+ seq = seq.getDatasetSequence();
+ }
+ }
+ Annotation[] resAnnot = ala.annotations,
+ gappedAnnot = new Annotation[Math.max(
+ alignViewport.getAlignment().getWidth(),
+ gapMap.length)];
+ for (int p = 0, ap = start; ap < gappedAnnot.length; ap++)
+ {
+ if (gapMap != null && gapMap.length > ap && !gapMap[ap])
+ {
+ gappedAnnot[ap] = new Annotation("", "", ' ', Float.NaN);
+ }
+ else if (p < resAnnot.length)
+ {
+ gappedAnnot[ap] = resAnnot[p++];
+ }
+ }
+ ala.sequenceRef = aseq;
+ ala.annotations = gappedAnnot;
+ AlignmentAnnotation newAnnot = getAlignViewport().getAlignment()
+ .updateFromOrCopyAnnotation(ala);
+ if (aseq != null)
+ {
+
+ aseq.addAlignmentAnnotation(newAnnot);
+ newAnnot.adjustForAlignment();
+
+ AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(
+ newAnnot, newAnnot.label, newAnnot.getCalcId());
+ }
+ newAnnots.add(newAnnot);
+
+ }
+ for (SequenceI sq : running.getSeqs())
+ {
+ if (!sq.getFeatures().hasFeatures()
- && (sq.getDBRefs() == null || sq.getDBRefs().length == 0))
++ && (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
+ {
+ continue;
+ }
+ running.setTransferSequenceFeatures(true);
+ SequenceI seq = running.getSeqNames().get(sq.getName());
+ SequenceI dseq;
+ ContiguousI seqRange = seq.findPositions(start, end);
+
+ while ((dseq = seq).getDatasetSequence() != null)
+ {
+ seq = seq.getDatasetSequence();
+ }
+ List<ContiguousI> sourceRange = new ArrayList();
+ if (gapMap != null && gapMap.length >= end)
+ {
+ int lastcol = start, col = start;
+ do
+ {
+ if (col == end || !gapMap[col])
+ {
+ if (lastcol <= (col - 1))
+ {
+ seqRange = seq.findPositions(lastcol, col);
+ sourceRange.add(seqRange);
+ }
+ lastcol = col + 1;
+ }
+ } while (++col <= end);
+ }
+ else
+ {
+ sourceRange.add(seq.findPositions(start, end));
+ }
+ int i = 0;
+ int source_startend[] = new int[sourceRange.size() * 2];
+
+ for (ContiguousI range : sourceRange)
+ {
+ source_startend[i++] = range.getBegin();
+ source_startend[i++] = range.getEnd();
+ }
+ Mapping mp = new Mapping(
+ new MapList(source_startend, new int[]
+ { seq.getStart(), seq.getEnd() }, 1, 1));
+ dseq.transferAnnotation(sq, mp);
+
+ }
+ updateOurAnnots(newAnnots);
+ }
+ }
+
+ /**
+ * notify manager that we have started, and wait for a free calculation slot
+ *
+ * @return true if slot is obtained and work still valid, false if another
+ * thread has done our work for us.
+ */
+ protected boolean checkDone()
+ {
+ calcMan.notifyStart(this);
+ ap.paintAlignment(false, false);
+ while (!calcMan.notifyWorking(this))
+ {
+ if (calcMan.isWorking(this))
+ {
+ return true;
+ }
+ try
+ {
+ if (ap != null)
+ {
+ ap.paintAlignment(false, false);
+ }
+
+ Thread.sleep(200);
+ } catch (Exception ex)
+ {
+ ex.printStackTrace();
+ }
+ }
+ if (alignViewport.isClosed())
+ {
+ abortAndDestroy();
+ return true;
+ }
+ return false;
+ }
+
+ protected void updateOurAnnots(List<AlignmentAnnotation> ourAnnot)
+ {
+ List<AlignmentAnnotation> our = ourAnnots;
+ ourAnnots = ourAnnot;
+ AlignmentI alignment = alignViewport.getAlignment();
+ if (our != null)
+ {
+ if (our.size() > 0)
+ {
+ for (AlignmentAnnotation an : our)
+ {
+ if (!ourAnnots.contains(an))
+ {
+ // remove the old annotation
+ alignment.deleteAnnotation(an);
+ }
+ }
+ }
+ our.clear();
+ }
+
+ // validate rows and update Alignmment state
+ for (AlignmentAnnotation an : ourAnnots)
+ {
+ alignViewport.getAlignment().validateAnnotation(an);
+ }
+ // TODO: may need a menu refresh after this
+ // af.setMenusForViewport();
+ ap.adjustAnnotationHeight();
+
+ }
+
+ public SequenceAnnotationServiceI getService()
+ {
+ return annotService;
+ }
+
+ }
--- /dev/null
+package org.json;
+
+import java.io.Closeable;
+
+/*
+ *
+ * Note: This file has been adapted for SwingJS by Bob Hanson hansonr@stolaf.edu
+ *
+ Copyright (c) 2002 JSON.org
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ The Software shall be used for Good, not Evil.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+ */
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
- import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.ResourceBundle;
+import java.util.Set;
+
+/**
+ * A JSONObject is an unordered collection of name/value pairs. Its external
+ * form is a string wrapped in curly braces with colons between the names and
+ * values, and commas between the values and names. The internal form is an
+ * object having <code>get</code> and <code>opt</code> methods for accessing the
+ * values by name, and <code>put</code> methods for adding or replacing values
+ * by name. The values can be any of these types: <code>Boolean</code>,
+ * <code>JSONArray</code>, <code>JSONObject</code>, <code>Number</code>,
+ * <code>String</code>, or the <code>JSONObject.NULL</code> object. A JSONObject
+ * constructor can be used to convert an external form JSON text into an
+ * internal form whose values can be retrieved with the <code>get</code> and
+ * <code>opt</code> methods, or to convert values into a JSON text using the
+ * <code>put</code> and <code>toString</code> methods. A <code>get</code> method
+ * returns a value if one can be found, and throws an exception if one cannot be
+ * found. An <code>opt</code> method returns a default value instead of throwing
+ * an exception, and so is useful for obtaining optional values.
+ * <p>
+ * The generic <code>get()</code> and <code>opt()</code> methods return an
+ * object, which you can cast or query for type. There are also typed
+ * <code>get</code> and <code>opt</code> methods that do type checking and type
+ * coercion for you. The opt methods differ from the get methods in that they do
+ * not throw. Instead, they return a specified value, such as null.
+ * <p>
+ * The <code>put</code> methods add or replace values in an object. For example,
+ *
+ * <pre>
+ * myString = new JSONObject().put("JSON", "Hello, World!").toString();
+ * </pre>
+ *
+ * produces the string <code>{"JSON": "Hello, World"}</code>.
+ * <p>
+ * The texts produced by the <code>toString</code> methods strictly conform to
+ * the JSON syntax rules. The constructors are more forgiving in the texts they
+ * will accept:
+ * <ul>
+ * <li>An extra <code>,</code> <small>(comma)</small> may appear just
+ * before the closing brace.</li>
+ * <li>Strings may be quoted with <code>'</code> <small>(single
+ * quote)</small>.</li>
+ * <li>Strings do not need to be quoted at all if they do not begin with a quote
+ * or single quote, and if they do not contain leading or trailing spaces, and
+ * if they do not contain any of these characters:
+ * <code>{ } [ ] / \ : , #</code> and if they do not look like numbers and if
+ * they are not the reserved words <code>true</code>, <code>false</code>, or
+ * <code>null</code>.</li>
+ * </ul>
+ *
+ * @author JSON.org
+ * @version 2016-08-15
+ */
+public class JSONObject {
+ /**
+ * JSONObject.NULL is equivalent to the value that JavaScript calls null, whilst
+ * Java's null is equivalent to the value that JavaScript calls undefined.
+ */
+ private static final class Null {
+
+ /**
+ * There is only intended to be a single instance of the NULL object, so the
+ * clone method returns itself.
+ *
+ * @return NULL.
+ */
+ @Override
+ protected final Object clone() {
+ return this;
+ }
+
+ /**
+ * A Null object is equal to the null value and to itself.
+ *
+ * @param object An object to test for nullness.
+ * @return true if the object parameter is the JSONObject.NULL object or null.
+ */
+ @Override
+ public boolean equals(Object object) {
+ return object == null || object == this;
+ }
+
+ /**
+ * A Null object is equal to the null value and to itself.
+ *
+ * @return always returns 0.
+ */
+ @Override
+ public int hashCode() {
+ return 0;
+ }
+
+ /**
+ * Get the "null" string value.
+ *
+ * @return The string "null".
+ */
+ @Override
+ public String toString() {
+ return "null";
+ }
+ }
+
+ /**
+ * The map where the JSONObject's properties are kept.
+ */
+ private final Map<String, Object> map;
+
+ /**
+ * It is sometimes more convenient and less ambiguous to have a
+ * <code>NULL</code> object than to use Java's <code>null</code> value.
+ * <code>JSONObject.NULL.equals(null)</code> returns <code>true</code>.
+ * <code>JSONObject.NULL.toString()</code> returns <code>"null"</code>.
+ */
+ public static final Object NULL = new Null();
+
+ /**
+ * Construct an empty JSONObject.
+ */
+ public JSONObject() {
+ // HashMap is used on purpose to ensure that elements are unordered by
+ // the specification.
+ // JSON tends to be a portable transfer format to allows the container
+ // implementations to rearrange their items for a faster element
+ // retrieval based on associative access.
+ // Therefore, an implementation mustn't rely on the order of the item.
+ this.map = new HashMap<String, Object>();
+ }
+
+ /**
+ * Construct a JSONObject from a subset of another JSONObject. An array of
+ * strings is used to identify the keys that should be copied. Missing keys are
+ * ignored.
+ *
+ * @param jo A JSONObject.
+ * @param names An array of strings.
+ */
+ public JSONObject(JSONObject jo, String[] names) {
+ this(names.length);
+ for (int i = 0; i < names.length; i += 1) {
+ try {
+ this.putOnce(names[i], jo.opt(names[i]));
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
+ /**
+ * Construct a JSONObject from a JSONTokener.
+ *
+ * @param x A JSONTokener object containing the source string.
+ * @throws JSONException If there is a syntax error in the source string or a
+ * duplicated key.
+ */
+ public JSONObject(JSONTokener x) throws JSONException {
+ this();
+ char c;
+ String key;
+
+ if (x.nextClean() != '{') {
+ throw x.syntaxError("A JSONObject text must begin with '{'");
+ }
+ for (;;) {
+ c = x.nextClean();
+ switch (c) {
+ case 0:
+ throw x.syntaxError("A JSONObject text must end with '}'");
+ case '}':
+ return;
+ default:
+ x.back();
+ key = x.nextValue().toString();
+ }
+
+ // The key is followed by ':'.
+
+ c = x.nextClean();
+ if (c != ':') {
+ throw x.syntaxError("Expected a ':' after a key");
+ }
+
+ // Use syntaxError(..) to include error location
+
+ if (key != null) {
+ // Check if key exists
+ if (this.opt(key) != null) {
+ // key already exists
+ throw x.syntaxError("Duplicate key \"" + key + "\"");
+ }
+ // Only add value if non-null
+ Object value = x.nextValue();
+ if (value != null) {
+ this.put(key, value);
+ }
+ }
+
+ // Pairs are separated by ','.
+
+ switch (x.nextClean()) {
+ case ';':
+ case ',':
+ if (x.nextClean() == '}') {
+ return;
+ }
+ x.back();
+ break;
+ case '}':
+ return;
+ default:
+ throw x.syntaxError("Expected a ',' or '}'");
+ }
+ }
+ }
+
+ /**
+ * Construct a JSONObject from a Map.
+ *
+ * @param m A map object that can be used to initialize the contents of the
+ * JSONObject.
+ * @throws JSONException If a value in the map is non-finite number.
+ * @throws NullPointerException If a key in the map is <code>null</code>
+ */
+ public JSONObject(Map<?, ?> m) {
+ if (m == null) {
+ this.map = new HashMap<String, Object>();
+ } else {
+ this.map = new HashMap<String, Object>(m.size());
+ for (final Entry<?, ?> e : m.entrySet()) {
+ if (e.getKey() == null) {
+ throw new NullPointerException("Null key.");
+ }
+ final Object value = e.getValue();
+ if (value != null) {
+ this.map.put(String.valueOf(e.getKey()), wrap(value));
+ }
+ }
+ }
+ }
+
+ /**
+ * Construct a JSONObject from an Object using bean getters. It reflects on all
+ * of the public methods of the object. For each of the methods with no
+ * parameters and a name starting with <code>"get"</code> or <code>"is"</code>
+ * followed by an uppercase letter, the method is invoked, and a key and the
+ * value returned from the getter method are put into the new JSONObject.
+ * <p>
+ * The key is formed by removing the <code>"get"</code> or <code>"is"</code>
+ * prefix. If the second remaining character is not upper case, then the first
+ * character is converted to lower case.
+ * <p>
+ * Methods that are <code>static</code>, return <code>void</code>, have
+ * parameters, or are "bridge" methods, are ignored.
+ * <p>
+ * For example, if an object has a method named <code>"getName"</code>, and if
+ * the result of calling <code>object.getName()</code> is
+ * <code>"Larry Fine"</code>, then the JSONObject will contain
+ * <code>"name": "Larry Fine"</code>.
+ * <p>
+ * The {@link JSONPropertyName} annotation can be used on a bean getter to
+ * override key name used in the JSONObject. For example, using the object above
+ * with the <code>getName</code> method, if we annotated it with:
+ *
+ * <pre>
+ * @JSONPropertyName("FullName")
+ * public String getName() {
+ * return this.name;
+ * }
+ * </pre>
+ *
+ * The resulting JSON object would contain <code>"FullName": "Larry Fine"</code>
+ * <p>
+ * Similarly, the {@link JSONPropertyName} annotation can be used on non-
+ * <code>get</code> and <code>is</code> methods. We can also override key name
+ * used in the JSONObject as seen below even though the field would normally be
+ * ignored:
+ *
+ * <pre>
+ * @JSONPropertyName("FullName")
+ * public String fullName() {
+ * return this.name;
+ * }
+ * </pre>
+ *
+ * The resulting JSON object would contain <code>"FullName": "Larry Fine"</code>
+ * <p>
+ * The {@link JSONPropertyIgnore} annotation can be used to force the bean
+ * property to not be serialized into JSON. If both {@link JSONPropertyIgnore}
+ * and {@link JSONPropertyName} are defined on the same method, a depth
+ * comparison is performed and the one closest to the concrete class being
+ * serialized is used. If both annotations are at the same level, then the
+ * {@link JSONPropertyIgnore} annotation takes precedent and the field is not
+ * serialized. For example, the following declaration would prevent the
+ * <code>getName</code> method from being serialized:
+ *
+ * <pre>
+ * @JSONPropertyName("FullName")
+ * @JSONPropertyIgnore
+ * public String getName() {
+ * return this.name;
+ * }
+ * </pre>
+ * <p>
+ *
+ * @param bean An object that has getter methods that should be used to make a
+ * JSONObject.
+ */
+ public JSONObject(Object bean) {
+ this();
+ this.populateMap(bean);
+ }
+
+ /**
+ * Construct a JSONObject from an Object, using reflection to find the public
+ * members. The resulting JSONObject's keys will be the strings from the names
+ * array, and the values will be the field values associated with those keys in
+ * the object. If a key is not found or not visible, then it will not be copied
+ * into the new JSONObject.
+ *
+ * @param object An object that has fields that should be used to make a
+ * JSONObject.
+ * @param names An array of strings, the names of the fields to be obtained
+ * from the object.
+ */
+ public JSONObject(Object object, String names[]) {
+ this(names.length);
+ Class<?> c = object.getClass();
+ for (int i = 0; i < names.length; i += 1) {
+ String name = names[i];
+ try {
+ this.putOpt(name, c.getField(name).get(object));
+ } catch (Exception ignore) {
+ }
+ }
+ }
+
+ /**
+ * Construct a JSONObject from a source JSON text string. This is the most
+ * commonly used JSONObject constructor.
+ *
+ * @param source A string beginning with <code>{</code> <small>(left
+ * brace)</small> and ending with <code>}</code>
+ * <small>(right brace)</small>.
+ * @exception JSONException If there is a syntax error in the source string or a
+ * duplicated key.
+ */
+ public JSONObject(String source) throws JSONException {
+ this(new JSONTokener(source));
+ }
+
+ /**
+ * Construct a JSONObject from a ResourceBundle.
+ *
+ * @param baseName The ResourceBundle base name.
+ * @param locale The Locale to load the ResourceBundle for.
+ * @throws JSONException If any JSONExceptions are detected.
+ */
+ public JSONObject(String baseName, Locale locale) throws JSONException {
+ this();
+ ResourceBundle bundle = ResourceBundle.getBundle(baseName, locale,
+ Thread.currentThread().getContextClassLoader());
+
+// Iterate through the keys in the bundle.
+
+ Enumeration<String> keys = bundle.getKeys();
+ while (keys.hasMoreElements()) {
+ Object key = keys.nextElement();
+ if (key != null) {
+
+// Go through the path, ensuring that there is a nested JSONObject for each
+// segment except the last. Add the value using the last segment's name into
+// the deepest nested JSONObject.
+
+ String[] path = ((String) key).split("\\.");
+ int last = path.length - 1;
+ JSONObject target = this;
+ for (int i = 0; i < last; i += 1) {
+ String segment = path[i];
+ JSONObject nextTarget = target.optJSONObject(segment);
+ if (nextTarget == null) {
+ nextTarget = new JSONObject();
+ target.put(segment, nextTarget);
+ }
+ target = nextTarget;
+ }
+ target.put(path[last], bundle.getString((String) key));
+ }
+ }
+ }
+
+ /**
+ * Constructor to specify an initial capacity of the internal map. Useful for
+ * library internal calls where we know, or at least can best guess, how big
+ * this JSONObject will be.
+ *
+ * @param initialCapacity initial capacity of the internal map.
+ */
+ protected JSONObject(int initialCapacity) {
+ this.map = new HashMap<String, Object>(initialCapacity);
+ }
+
+ /**
+ * Accumulate values under a key. It is similar to the put method except that if
+ * there is already an object stored under the key then a JSONArray is stored
+ * under the key to hold all of the accumulated values. If there is already a
+ * JSONArray, then the new value is appended to it. In contrast, the put method
+ * replaces the previous value.
+ *
+ * If only one value is accumulated that is not a JSONArray, then the result
+ * will be the same as using put. But if multiple values are accumulated, then
+ * the result will be like append.
+ *
+ * @param key A key string.
+ * @param value An object to be accumulated under the key.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject accumulate(String key, Object value) throws JSONException {
+ testValidity(value);
+ Object object = this.opt(key);
+ if (object == null) {
+ this.put(key, value instanceof JSONArray ? new JSONArray().put(value) : value);
+ } else if (object instanceof JSONArray) {
+ ((JSONArray) object).put(value);
+ } else {
+ this.put(key, new JSONArray().put(object).put(value));
+ }
+ return this;
+ }
+
+ /**
+ * Append values to the array under a key. If the key does not exist in the
+ * JSONObject, then the key is put in the JSONObject with its value being a
+ * JSONArray containing the value parameter. If the key was already associated
+ * with a JSONArray, then the value parameter is appended to it.
+ *
+ * @param key A key string.
+ * @param value An object to be accumulated under the key.
+ * @return this.
+ * @throws JSONException If the value is non-finite number or if the
+ * current value associated with the key is not a
+ * JSONArray.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject append(String key, Object value) throws JSONException {
+ testValidity(value);
+ Object object = this.opt(key);
+ if (object == null) {
+ this.put(key, new JSONArray().put(value));
+ } else if (object instanceof JSONArray) {
+ this.put(key, ((JSONArray) object).put(value));
+ } else {
+ throw new JSONException("JSONObject[" + key + "] is not a JSONArray.");
+ }
+ return this;
+ }
+
+ /**
+ * Produce a string from a double. The string "null" will be returned if the
+ * number is not finite.
+ *
+ * @param d A double.
+ * @return A String.
+ */
+ public static String doubleToString(double d) {
+ if (Double.isInfinite(d) || Double.isNaN(d)) {
+ return "null";
+ }
+
+// Shave off trailing zeros and decimal point, if possible.
+
+ String string = Double.toString(d);
+ if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) {
+ while (string.endsWith("0")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ if (string.endsWith(".")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Get the value object associated with a key.
+ *
+ * @param key A key string.
+ * @return The object associated with the key.
+ * @throws JSONException if the key is not found.
+ */
+ public Object get(String key) throws JSONException {
+ if (key == null) {
+ throw new JSONException("Null key.");
+ }
+ Object object = this.opt(key);
+ if (object == null) {
+ throw new JSONException("JSONObject[" + quote(key) + "] not found.");
+ }
+ return object;
+ }
+
+ /**
+ * Get the enum value associated with a key.
+ *
+ * @param clazz The type of enum to retrieve.
+ * @param key A key string.
+ * @return The enum value associated with the key
+ * @throws JSONException if the key is not found or if the value cannot be
+ * converted to an enum.
+ */
+ public <E extends Enum<E>> E getEnum(Class<E> clazz, String key) throws JSONException {
+ E val = optEnum(clazz, key);
+ if (val == null) {
+ // JSONException should really take a throwable argument.
+ // If it did, I would re-implement this with the Enum.valueOf
+ // method and place any thrown exception in the JSONException
+ throw new JSONException(
+ "JSONObject[" + quote(key) + "] is not an enum of type " + quote(clazz.getSimpleName()) + ".");
+ }
+ return val;
+ }
+
+ /**
+ * Get the boolean value associated with a key.
+ *
+ * @param key A key string.
+ * @return The truth.
+ * @throws JSONException if the value is not a Boolean or the String "true" or
+ * "false".
+ */
+ public boolean getBoolean(String key) throws JSONException {
+ Object object = this.get(key);
+ if (object.equals(Boolean.FALSE) || (object instanceof String && ((String) object).equalsIgnoreCase("false"))) {
+ return false;
+ } else if (object.equals(Boolean.TRUE)
+ || (object instanceof String && ((String) object).equalsIgnoreCase("true"))) {
+ return true;
+ }
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a Boolean.");
+ }
+
+ /**
+ * Get the BigInteger value associated with a key.
+ *
+ * @param key A key string.
+ * @return The numeric value.
+ * @throws JSONException if the key is not found or if the value cannot be
+ * converted to BigInteger.
+ */
+ public BigInteger getBigInteger(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ return new BigInteger(object.toString());
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigInteger.", e);
+ }
+ }
+
+ /**
+ * Get the BigDecimal value associated with a key.
+ *
+ * @param key A key string.
+ * @return The numeric value.
+ * @throws JSONException if the key is not found or if the value cannot be
+ * converted to BigDecimal.
+ */
+ public BigDecimal getBigDecimal(String key) throws JSONException {
+ Object object = this.get(key);
+ if (object instanceof BigDecimal) {
+ return (BigDecimal) object;
+ }
+ try {
+ return new BigDecimal(object.toString());
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] could not be converted to BigDecimal.", e);
+ }
+ }
+
+ /**
+ * Get the double value associated with a key.
+ *
+ * @param key A key string.
+ * @return The numeric value.
+ * @throws JSONException if the key is not found or if the value is not a Number
+ * object and cannot be converted to a number.
+ */
+ public double getDouble(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ return object instanceof Number ? ((Number) object).doubleValue() : Double.parseDouble(object.toString());
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e);
+ }
+ }
+
+ /**
+ * Get the float value associated with a key.
+ *
+ * @param key A key string.
+ * @return The numeric value.
+ * @throws JSONException if the key is not found or if the value is not a Number
+ * object and cannot be converted to a number.
+ */
+ public float getFloat(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ return object instanceof Number ? ((Number) object).floatValue() : Float.parseFloat(object.toString());
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e);
+ }
+ }
+
+ /**
+ * Get the Number value associated with a key.
+ *
+ * @param key A key string.
+ * @return The numeric value.
+ * @throws JSONException if the key is not found or if the value is not a Number
+ * object and cannot be converted to a number.
+ */
+ public Number getNumber(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ if (object instanceof Number) {
+ return (Number) object;
+ }
+ return stringToNumber(object.toString());
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a number.", e);
+ }
+ }
+
+ /**
+ * Get the int value associated with a key.
+ *
+ * @param key A key string.
+ * @return The integer value.
+ * @throws JSONException if the key is not found or if the value cannot be
+ * converted to an integer.
+ */
+ public int getInt(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ return object instanceof Number ? ((Number) object).intValue() : Integer.parseInt((String) object);
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] is not an int.", e);
+ }
+ }
+
+ /**
+ * Get the JSONArray value associated with a key.
+ *
+ * @param key A key string.
+ * @return A JSONArray which is the value.
+ * @throws JSONException if the key is not found or if the value is not a
+ * JSONArray.
+ */
+ public JSONArray getJSONArray(String key) throws JSONException {
+ Object object = this.get(key);
+ if (object instanceof JSONArray) {
+ return (JSONArray) object;
+ }
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONArray.");
+ }
+
+ /**
+ * Get the JSONObject value associated with a key.
+ *
+ * @param key A key string.
+ * @return A JSONObject which is the value.
+ * @throws JSONException if the key is not found or if the value is not a
+ * JSONObject.
+ */
+ public JSONObject getJSONObject(String key) throws JSONException {
+ Object object = this.get(key);
+ if (object instanceof JSONObject) {
+ return (JSONObject) object;
+ }
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a JSONObject.");
+ }
+
+ /**
+ * Get the long value associated with a key.
+ *
+ * @param key A key string.
+ * @return The long value.
+ * @throws JSONException if the key is not found or if the value cannot be
+ * converted to a long.
+ */
+ public long getLong(String key) throws JSONException {
+ Object object = this.get(key);
+ try {
+ return object instanceof Number ? ((Number) object).longValue() : Long.parseLong((String) object);
+ } catch (Exception e) {
+ throw new JSONException("JSONObject[" + quote(key) + "] is not a long.", e);
+ }
+ }
+
+ /**
+ * Get an array of field names from a JSONObject.
+ *
+ * @return An array of field names, or null if there are no names.
+ */
+ public static String[] getNames(JSONObject jo) {
+ if (jo.isEmpty()) {
+ return null;
+ }
+ return jo.keySet().toArray(new String[jo.length()]);
+ }
+
+ /**
+ * Get an array of field names from an Object.
+ *
+ * @return An array of field names, or null if there are no names.
+ */
+ public static String[] getNames(Object object) {
+ if (object == null) {
+ return null;
+ }
+ Class<?> klass = object.getClass();
+ Field[] fields = klass.getFields();
+ int length = fields.length;
+ if (length == 0) {
+ return null;
+ }
+ String[] names = new String[length];
+ for (int i = 0; i < length; i += 1) {
+ names[i] = fields[i].getName();
+ }
+ return names;
+ }
+
+ /**
+ * Get the string associated with a key.
+ *
+ * @param key A key string.
+ * @return A string which is the value.
+ * @throws JSONException if there is no string value for the key.
+ */
+ public String getString(String key) throws JSONException {
+ Object object = this.get(key);
+ if (object instanceof String) {
+ return (String) object;
+ }
+ throw new JSONException("JSONObject[" + quote(key) + "] not a string.");
+ }
+
+ /**
+ * Determine if the JSONObject contains a specific key.
+ *
+ * @param key A key string.
+ * @return true if the key exists in the JSONObject.
+ */
+ public boolean has(String key) {
+ return this.map.containsKey(key);
+ }
+
+ /**
+ * Increment a property of a JSONObject. If there is no such property, create
+ * one with a value of 1. If there is such a property, and if it is an Integer,
+ * Long, Double, or Float, then add one to it.
+ *
+ * @param key A key string.
+ * @return this.
+ * @throws JSONException If there is already a property with this name that is
+ * not an Integer, Long, Double, or Float.
+ */
+ public JSONObject increment(String key) throws JSONException {
+ Object value = this.opt(key);
+ if (value == null) {
+ this.put(key, 1);
+ } else if (value instanceof BigInteger) {
+ this.put(key, ((BigInteger) value).add(BigInteger.ONE));
+ } else if (value instanceof BigDecimal) {
+ this.put(key, ((BigDecimal) value).add(BigDecimal.ONE));
+ } else if (value instanceof Integer) {
+ this.put(key, ((Integer) value).intValue() + 1);
+ } else if (value instanceof Long) {
+ this.put(key, ((Long) value).longValue() + 1L);
+ } else if (value instanceof Double) {
+ this.put(key, ((Double) value).doubleValue() + 1.0d);
+ } else if (value instanceof Float) {
+ this.put(key, ((Float) value).floatValue() + 1.0f);
+ } else {
+ throw new JSONException("Unable to increment [" + quote(key) + "].");
+ }
+ return this;
+ }
+
+ /**
+ * Determine if the value associated with the key is <code>null</code> or if
+ * there is no value.
+ *
+ * @param key A key string.
+ * @return true if there is no value associated with the key or if the value is
+ * the JSONObject.NULL object.
+ */
+ public boolean isNull(String key) {
+ return JSONObject.NULL.equals(this.opt(key));
+ }
+
+ /**
+ * Get an enumeration of the keys of the JSONObject. Modifying this key Set will
+ * also modify the JSONObject. Use with caution.
+ *
+ * @see Set#iterator()
+ *
+ * @return An iterator of the keys.
+ */
+ public Iterator<String> keys() {
+ return this.keySet().iterator();
+ }
+
+ /**
+ * Get a set of keys of the JSONObject. Modifying this key Set will also modify
+ * the JSONObject. Use with caution.
+ *
+ * @see Map#keySet()
+ *
+ * @return A keySet.
+ */
+ public Set<String> keySet() {
+ return this.map.keySet();
+ }
+
+ /**
+ * Get a set of entries of the JSONObject. These are raw values and may not
+ * match what is returned by the JSONObject get* and opt* functions. Modifying
+ * the returned EntrySet or the Entry objects contained therein will modify the
+ * backing JSONObject. This does not return a clone or a read-only view.
+ *
+ * Use with caution.
+ *
+ * @see Map#entrySet()
+ *
+ * @return An Entry Set
+ */
+ protected Set<Entry<String, Object>> entrySet() {
+ return this.map.entrySet();
+ }
+
+ /**
+ * Get the number of keys stored in the JSONObject.
+ *
+ * @return The number of keys in the JSONObject.
+ */
+ public int length() {
+ return this.map.size();
+ }
+
+ /**
+ * Check if JSONObject is empty.
+ *
+ * @return true if JSONObject is empty, otherwise false.
+ */
+ public boolean isEmpty() {
+ return map.isEmpty();
+ }
+
+ /**
+ * Produce a JSONArray containing the names of the elements of this JSONObject.
+ *
+ * @return A JSONArray containing the key strings, or null if the JSONObject is
+ * empty.
+ */
+ public JSONArray names() {
+ if (this.map.isEmpty()) {
+ return null;
+ }
+ return new JSONArray(this.map.keySet());
+ }
+
+ /**
+ * Produce a string from a Number.
+ *
+ * @param number A Number
+ * @return A String.
+ * @throws JSONException If n is a non-finite number.
+ */
+ public static String numberToString(Number number) throws JSONException {
+ if (number == null) {
+ throw new JSONException("Null pointer");
+ }
+ testValidity(number);
+
+ // Shave off trailing zeros and decimal point, if possible.
+
+ String string = number.toString();
+ if (string.indexOf('.') > 0 && string.indexOf('e') < 0 && string.indexOf('E') < 0) {
+ while (string.endsWith("0")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ if (string.endsWith(".")) {
+ string = string.substring(0, string.length() - 1);
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Get an optional value associated with a key.
+ *
+ * @param key A key string.
+ * @return An object which is the value, or null if there is no value.
+ */
+ public Object opt(String key) {
+ return key == null ? null : this.map.get(key);
+ }
+
+ /**
+ * Get the enum value associated with a key.
+ *
+ * @param clazz The type of enum to retrieve.
+ * @param key A key string.
+ * @return The enum value associated with the key or null if not found
+ */
+ public <E extends Enum<E>> E optEnum(Class<E> clazz, String key) {
+ return this.optEnum(clazz, key, null);
+ }
+
+ /**
+ * Get the enum value associated with a key.
+ *
+ * @param clazz The type of enum to retrieve.
+ * @param key A key string.
+ * @param defaultValue The default in case the value is not found
+ * @return The enum value associated with the key or defaultValue if the value
+ * is not found or cannot be assigned to <code>clazz</code>
+ */
+ public <E extends Enum<E>> E optEnum(Class<E> clazz, String key, E defaultValue) {
+ try {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (clazz.isAssignableFrom(val.getClass())) {
+ // we just checked it!
+ @SuppressWarnings("unchecked")
+ E myE = (E) val;
+ return myE;
+ }
+ return Enum.valueOf(clazz, val.toString());
+ } catch (IllegalArgumentException e) {
+ return defaultValue;
+ } catch (NullPointerException e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Get an optional boolean associated with a key. It returns false if there is
+ * no such key, or if the value is not Boolean.TRUE or the String "true".
+ *
+ * @param key A key string.
+ * @return The truth.
+ */
+ public boolean optBoolean(String key) {
+ return this.optBoolean(key, false);
+ }
+
+ /**
+ * Get an optional boolean associated with a key. It returns the defaultValue if
+ * there is no such key, or if it is not a Boolean or the String "true" or
+ * "false" (case insensitive).
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return The truth.
+ */
+ public boolean optBoolean(String key, boolean defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Boolean) {
+ return ((Boolean) val).booleanValue();
+ }
+ try {
+ // we'll use the get anyway because it does string conversion.
+ return this.getBoolean(key);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Get an optional BigDecimal associated with a key, or the defaultValue if
+ * there is no such key or if its value is not a number. If the value is a
+ * string, an attempt will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public BigDecimal optBigDecimal(String key, BigDecimal defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof BigDecimal) {
+ return (BigDecimal) val;
+ }
+ if (val instanceof BigInteger) {
+ return new BigDecimal((BigInteger) val);
+ }
+ if (val instanceof Double || val instanceof Float) {
+ return new BigDecimal(((Number) val).doubleValue());
+ }
+ if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte) {
+ return new BigDecimal(((Number) val).longValue());
+ }
+ // don't check if it's a string in case of unchecked Number subclasses
+ try {
+ return new BigDecimal(val.toString());
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Get an optional BigInteger associated with a key, or the defaultValue if
+ * there is no such key or if its value is not a number. If the value is a
+ * string, an attempt will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public BigInteger optBigInteger(String key, BigInteger defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof BigInteger) {
+ return (BigInteger) val;
+ }
+ if (val instanceof BigDecimal) {
+ return ((BigDecimal) val).toBigInteger();
+ }
+ if (val instanceof Double || val instanceof Float) {
+ return new BigDecimal(((Number) val).doubleValue()).toBigInteger();
+ }
+ if (val instanceof Long || val instanceof Integer || val instanceof Short || val instanceof Byte) {
+ return BigInteger.valueOf(((Number) val).longValue());
+ }
+ // don't check if it's a string in case of unchecked Number subclasses
+ try {
+ // the other opt functions handle implicit conversions, i.e.
+ // jo.put("double",1.1d);
+ // jo.optInt("double"); -- will return 1, not an error
+ // this conversion to BigDecimal then to BigInteger is to maintain
+ // that type cast support that may truncate the decimal.
+ final String valStr = val.toString();
+ if (isDecimalNotation(valStr)) {
+ return new BigDecimal(valStr).toBigInteger();
+ }
+ return new BigInteger(valStr);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+
+ /**
+ * Get an optional double associated with a key, or NaN if there is no such key
+ * or if its value is not a number. If the value is a string, an attempt will be
+ * made to evaluate it as a number.
+ *
+ * @param key A string which is the key.
+ * @return An object which is the value.
+ */
+ public double optDouble(String key) {
+ return this.optDouble(key, Double.NaN);
+ }
+
+ /**
+ * Get an optional double associated with a key, or the defaultValue if there is
+ * no such key or if its value is not a number. If the value is a string, an
+ * attempt will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public double optDouble(String key, double defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).doubleValue();
+ }
+ if (val instanceof String) {
+ try {
+ return Double.parseDouble((String) val);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get the optional double value associated with an index. NaN is returned if
+ * there is no value for the index, or if the value is not a number and cannot
+ * be converted to a number.
+ *
+ * @param key A key string.
+ * @return The value.
+ */
+ public float optFloat(String key) {
+ return this.optFloat(key, Float.NaN);
+ }
+
+ /**
+ * Get the optional double value associated with an index. The defaultValue is
+ * returned if there is no value for the index, or if the value is not a number
+ * and cannot be converted to a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default value.
+ * @return The value.
+ */
+ public float optFloat(String key, float defaultValue) {
+ Object val = this.opt(key);
+ if (JSONObject.NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).floatValue();
+ }
+ if (val instanceof String) {
+ try {
+ return Float.parseFloat((String) val);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get an optional int value associated with a key, or zero if there is no such
+ * key or if the value is not a number. If the value is a string, an attempt
+ * will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @return An object which is the value.
+ */
+ public int optInt(String key) {
+ return this.optInt(key, 0);
+ }
+
+ /**
+ * Get an optional int value associated with a key, or the default if there is
+ * no such key or if the value is not a number. If the value is a string, an
+ * attempt will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public int optInt(String key, int defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).intValue();
+ }
+
+ if (val instanceof String) {
+ try {
+ return new BigDecimal((String) val).intValue();
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get an optional JSONArray associated with a key. It returns null if there is
+ * no such key, or if its value is not a JSONArray.
+ *
+ * @param key A key string.
+ * @return A JSONArray which is the value.
+ */
+ public JSONArray optJSONArray(String key) {
+ Object o = this.opt(key);
+ return o instanceof JSONArray ? (JSONArray) o : null;
+ }
+
+ /**
+ * Get an optional JSONObject associated with a key. It returns null if there is
+ * no such key, or if its value is not a JSONObject.
+ *
+ * @param key A key string.
+ * @return A JSONObject which is the value.
+ */
+ public JSONObject optJSONObject(String key) {
+ Object object = this.opt(key);
+ return object instanceof JSONObject ? (JSONObject) object : null;
+ }
+
+ /**
+ * Get an optional long value associated with a key, or zero if there is no such
+ * key or if the value is not a number. If the value is a string, an attempt
+ * will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @return An object which is the value.
+ */
+ public long optLong(String key) {
+ return this.optLong(key, 0);
+ }
+
+ /**
+ * Get an optional long value associated with a key, or the default if there is
+ * no such key or if the value is not a number. If the value is a string, an
+ * attempt will be made to evaluate it as a number.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public long optLong(String key, long defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Number) {
+ return ((Number) val).longValue();
+ }
+
+ if (val instanceof String) {
+ try {
+ return new BigDecimal((String) val).longValue();
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get an optional {@link Number} value associated with a key, or
+ * <code>null</code> if there is no such key or if the value is not a number. If
+ * the value is a string, an attempt will be made to evaluate it as a number
+ * ({@link BigDecimal}). This method would be used in cases where type coercion
+ * of the number value is unwanted.
+ *
+ * @param key A key string.
+ * @return An object which is the value.
+ */
+ public Number optNumber(String key) {
+ return this.optNumber(key, null);
+ }
+
+ /**
+ * Get an optional {@link Number} value associated with a key, or the default if
+ * there is no such key or if the value is not a number. If the value is a
+ * string, an attempt will be made to evaluate it as a number. This method would
+ * be used in cases where type coercion of the number value is unwanted.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return An object which is the value.
+ */
+ public Number optNumber(String key, Number defaultValue) {
+ Object val = this.opt(key);
+ if (NULL.equals(val)) {
+ return defaultValue;
+ }
+ if (val instanceof Number) {
+ return (Number) val;
+ }
+
+ if (val instanceof String) {
+ try {
+ return stringToNumber((String) val);
+ } catch (Exception e) {
+ return defaultValue;
+ }
+ }
+ return defaultValue;
+ }
+
+ /**
+ * Get an optional string associated with a key. It returns an empty string if
+ * there is no such key. If the value is not a string and is not null, then it
+ * is converted to a string.
+ *
+ * @param key A key string.
+ * @return A string which is the value.
+ */
+ public String optString(String key) {
+ return this.optString(key, "");
+ }
+
+ /**
+ * Get an optional string associated with a key. It returns the defaultValue if
+ * there is no such key.
+ *
+ * @param key A key string.
+ * @param defaultValue The default.
+ * @return A string which is the value.
+ */
+ public String optString(String key, String defaultValue) {
+ Object object = this.opt(key);
+ return NULL.equals(object) ? defaultValue : object.toString();
+ }
+
+ /**
+ * Populates the internal map of the JSONObject with the bean properties. The
+ * bean can not be recursive.
+ *
+ * @see JSONObject#JSONObject(Object)
+ *
+ * @param bean the bean
+ */
+ private void populateMap(Object bean) {
+ Class<?> klass = bean.getClass();
+
+ // If klass is a System class then set includeSuperClass to false.
+
+ boolean includeSuperClass = klass.getClassLoader() != null;
+
+ Method[] methods = includeSuperClass ? klass.getMethods() : klass.getDeclaredMethods();
+ for (final Method method : methods) {
+ final int modifiers = method.getModifiers();
+ if (Modifier.isPublic(modifiers) && !Modifier.isStatic(modifiers) && method.getParameterTypes().length == 0
+ && !method.isBridge() && method.getReturnType() != Void.TYPE
+ && isValidMethodName(method.getName())) {
+ final String key = getKeyNameFromMethod(method);
+ if (key != null && !key.isEmpty()) {
+ try {
+ final Object result = method.invoke(bean);
+ if (result != null) {
+ this.map.put(key, wrap(result));
+ // we don't use the result anywhere outside of wrap
+ // if it's a resource we should be sure to close it
+ // after calling toString
+ if (result instanceof Closeable) {
+ try {
+ ((Closeable) result).close();
+ } catch (IOException ignore) {
+ }
+ }
+ }
+ } catch (IllegalAccessException ignore) {
+ } catch (IllegalArgumentException ignore) {
+ } catch (InvocationTargetException ignore) {
+ }
+ }
+ }
+ }
+ }
+
+ private boolean isValidMethodName(String name) {
+ return !"getClass".equals(name) && !"getDeclaringClass".equals(name);
+ }
+
+ private String getKeyNameFromMethod(Method method) {
+ final int ignoreDepth = -1;// getAnnotationDepth(method, JSONPropertyIgnore.class);
+// if (ignoreDepth > 0) {
+// final int forcedNameDepth = getAnnotationDepth(method, JSONPropertyName.class);
+// if (forcedNameDepth < 0 || ignoreDepth <= forcedNameDepth) {
+// // the hierarchy asked to ignore, and the nearest name override
+// // was higher or non-existent
+// return null;
+// }
+// }
+// JSONPropertyName annotation = getAnnotation(method, JSONPropertyName.class);
+// if (annotation != null && annotation.value() != null && !annotation.value().isEmpty()) {
+// return annotation.value();
+// }
+ String key;
+ final String name = method.getName();
+ if (name.startsWith("get") && name.length() > 3) {
+ key = name.substring(3);
+ } else if (name.startsWith("is") && name.length() > 2) {
+ key = name.substring(2);
+ } else {
+ return null;
+ }
+ // if the first letter in the key is not uppercase, then skip.
+ // This is to maintain backwards compatibility before PR406
+ // (https://github.com/stleary/JSON-java/pull/406/)
+ if (Character.isLowerCase(key.charAt(0))) {
+ return null;
+ }
+ if (key.length() == 1) {
+ key = key.toLowerCase(Locale.ROOT);
+ } else if (!Character.isUpperCase(key.charAt(1))) {
+ key = key.substring(0, 1).toLowerCase(Locale.ROOT) + key.substring(1);
+ }
+ return (/** @j2sNative 1 ? key.split("$")[0] : */
+ key);
+ }
+
+// /**
+// * Searches the class hierarchy to see if the method or it's super
+// * implementations and interfaces has the annotation.
+// *
+// * @param <A>
+// * type of the annotation
+// *
+// * @param m
+// * method to check
+// * @param annotationClass
+// * annotation to look for
+// * @return the {@link Annotation} if the annotation exists on the current method
+// * or one of it's super class definitions
+// */
+// private static <A extends Annotation> A getAnnotation(final Method m, final Class<A> annotationClass) {
+// return null;
+// // if we have invalid data the result is null
+// if (true || m == null || annotationClass == null) {
+// return null;
+// }
+//
+// if (m.isAnnotationPresent(annotationClass)) {
+// return m.getAnnotation(annotationClass);
+// }
+//
+// // if we've already reached the Object class, return null;
+// Class<?> c = m.getDeclaringClass();
+// if (c.getSuperclass() == null) {
+// return null;
+// }
+//
+// // check directly implemented interfaces for the method being checked
+// for (Class<?> i : c.getInterfaces()) {
+// try {
+// Method im = i.getMethod(m.getName(), m.getParameterTypes());
+// return getAnnotation(im, annotationClass);
+// } catch (final SecurityException ex) {
+// continue;
+// } catch (final NoSuchMethodException ex) {
+// continue;
+// }
+// }
+//
+// try {
+// return getAnnotation(
+// c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+// annotationClass);
+// } catch (final SecurityException ex) {
+// return null;
+// } catch (final NoSuchMethodException ex) {
+// return null;
+// }
+// }
+//
+// /**
+// * Searches the class hierarchy to see if the method or it's super
+// * implementations and interfaces has the annotation. Returns the depth of the
+// * annotation in the hierarchy.
+// *
+// * @param <A>
+// * type of the annotation
+// *
+// * @param m
+// * method to check
+// * @param annotationClass
+// * annotation to look for
+// * @return Depth of the annotation or -1 if the annotation is not on the method.
+// */
+// private static int getAnnotationDepth(final Method m, final Class<? extends Annotation> annotationClass) {
+// // if we have invalid data the result is -1
+// if (m == null || annotationClass == null) {
+// return -1;
+// }
+// if (m.isAnnotationPresent(annotationClass)) {
+// return 1;
+// }
+//
+// // if we've already reached the Object class, return -1;
+// Class<?> c = m.getDeclaringClass();
+// if (c.getSuperclass() == null) {
+// return -1;
+// }
+//
+// // check directly implemented interfaces for the method being checked
+// for (Class<?> i : c.getInterfaces()) {
+// try {
+// Method im = i.getMethod(m.getName(), m.getParameterTypes());
+// int d = getAnnotationDepth(im, annotationClass);
+// if (d > 0) {
+// // since the annotation was on the interface, add 1
+// return d + 1;
+// }
+// } catch (final SecurityException ex) {
+// continue;
+// } catch (final NoSuchMethodException ex) {
+// continue;
+// }
+// }
+//
+// try {
+// int d = getAnnotationDepth(
+// c.getSuperclass().getMethod(m.getName(), m.getParameterTypes()),
+// annotationClass);
+// if (d > 0) {
+// // since the annotation was on the superclass, add 1
+// return d + 1;
+// }
+// return -1;
+// } catch (final SecurityException ex) {
+// return -1;
+// } catch (final NoSuchMethodException ex) {
+// return -1;
+// }
+// }
+
+ /**
+ * Put a key/boolean pair in the JSONObject.
+ *
+ * @param key A key string.
+ * @param value A boolean which is the value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, boolean value) throws JSONException {
+ return this.put(key, value ? Boolean.TRUE : Boolean.FALSE);
+ }
+
+ /**
+ * Put a key/value pair in the JSONObject, where the value will be a JSONArray
+ * which is produced from a Collection.
+ *
+ * @param key A key string.
+ * @param value A Collection value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, Collection<?> value) throws JSONException {
+ return this.put(key, new JSONArray(value));
+ }
+
+ /**
+ * Put a key/double pair in the JSONObject.
+ *
+ * @param key A key string.
+ * @param value A double which is the value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, double value) throws JSONException {
+ return this.put(key, Double.valueOf(value));
+ }
+
+ /**
+ * Put a key/float pair in the JSONObject.
+ *
+ * @param key A key string.
+ * @param value A float which is the value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, float value) throws JSONException {
+ return this.put(key, Float.valueOf(value));
+ }
+
+ /**
+ * Put a key/int pair in the JSONObject.
+ *
+ * @param key A key string.
+ * @param value An int which is the value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, int value) throws JSONException {
+ return this.put(key, Integer.valueOf(value));
+ }
+
+ /**
+ * Put a key/long pair in the JSONObject.
+ *
+ * @param key A key string.
+ * @param value A long which is the value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, long value) throws JSONException {
+ return this.put(key, Long.valueOf(value));
+ }
+
+ /**
+ * Put a key/value pair in the JSONObject, where the value will be a JSONObject
+ * which is produced from a Map.
+ *
+ * @param key A key string.
+ * @param value A Map value.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, Map<?, ?> value) throws JSONException {
+ return this.put(key, new JSONObject(value));
+ }
+
+ /**
+ * Put a key/value pair in the JSONObject. If the value is <code>null</code>,
+ * then the key will be removed from the JSONObject if it is present.
+ *
+ * @param key A key string.
+ * @param value An object which is the value. It should be of one of these
+ * types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
+ * String, or the JSONObject.NULL object.
+ * @return this.
+ * @throws JSONException If the value is non-finite number.
+ * @throws NullPointerException If the key is <code>null</code>.
+ */
+ public JSONObject put(String key, Object value) throws JSONException {
+ if (key == null) {
+ throw new NullPointerException("Null key.");
+ }
+ if (value != null) {
+ testValidity(value);
+ this.map.put(key, value);
+ } else {
+ this.remove(key);
+ }
+ return this;
+ }
+
+ /**
+ * Put a key/value pair in the JSONObject, but only if the key and the value are
+ * both non-null, and only if there is not already a member with that name.
+ *
+ * @param key string
+ * @param value object
+ * @return this.
+ * @throws JSONException if the key is a duplicate
+ */
+ public JSONObject putOnce(String key, Object value) throws JSONException {
+ if (key != null && value != null) {
+ if (this.opt(key) != null) {
+ throw new JSONException("Duplicate key \"" + key + "\"");
+ }
+ return this.put(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Put a key/value pair in the JSONObject, but only if the key and the value are
+ * both non-null.
+ *
+ * @param key A key string.
+ * @param value An object which is the value. It should be of one of these
+ * types: Boolean, Double, Integer, JSONArray, JSONObject, Long,
+ * String, or the JSONObject.NULL object.
+ * @return this.
+ * @throws JSONException If the value is a non-finite number.
+ */
+ public JSONObject putOpt(String key, Object value) throws JSONException {
+ if (key != null && value != null) {
+ return this.put(key, value);
+ }
+ return this;
+ }
+
+ /**
+ * Creates a JSONPointer using an initialization string and tries to match it to
+ * an item within this JSONObject. For example, given a JSONObject initialized
+ * with this document:
+ *
+ * <pre>
+ * {
+ * "a":{"b":"c"}
+ * }
+ * </pre>
+ *
+ * and this JSONPointer string:
+ *
+ * <pre>
+ * "/a/b"
+ * </pre>
+ *
+ * Then this method will return the String "c". A JSONPointerException may be
+ * thrown from code called by this method.
+ *
+ * @param jsonPointer string that can be used to create a JSONPointer
+ * @return the item matched by the JSONPointer, otherwise null
+ */
+ public Object query(String jsonPointer) {
+ return query(new JSONPointer(jsonPointer));
+ }
+
+ /**
+ * Uses a user initialized JSONPointer and tries to match it to an item within
+ * this JSONObject. For example, given a JSONObject initialized with this
+ * document:
+ *
+ * <pre>
+ * {
+ * "a":{"b":"c"}
+ * }
+ * </pre>
+ *
+ * and this JSONPointer:
+ *
+ * <pre>
+ * "/a/b"
+ * </pre>
+ *
+ * Then this method will return the String "c". A JSONPointerException may be
+ * thrown from code called by this method.
+ *
+ * @param jsonPointer string that can be used to create a JSONPointer
+ * @return the item matched by the JSONPointer, otherwise null
+ */
+ public Object query(JSONPointer jsonPointer) {
+ return jsonPointer.queryFrom(this);
+ }
+
+ /**
+ * Queries and returns a value from this object using {@code jsonPointer}, or
+ * returns null if the query fails due to a missing key.
+ *
+ * @param jsonPointer the string representation of the JSON pointer
+ * @return the queried value or {@code null}
+ * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+ */
+ public Object optQuery(String jsonPointer) {
+ return optQuery(new JSONPointer(jsonPointer));
+ }
+
+ /**
+ * Queries and returns a value from this object using {@code jsonPointer}, or
+ * returns null if the query fails due to a missing key.
+ *
+ * @param jsonPointer The JSON pointer
+ * @return the queried value or {@code null}
+ * @throws IllegalArgumentException if {@code jsonPointer} has invalid syntax
+ */
+ public Object optQuery(JSONPointer jsonPointer) {
+ try {
+ return jsonPointer.queryFrom(this);
+ } catch (JSONPointerException e) {
+ return null;
+ }
+ }
+
+ /**
+ * Produce a string in double quotes with backslash sequences in all the right
+ * places. A backslash will be inserted within </, producing <\/, allowing JSON
+ * text to be delivered in HTML. In JSON text, a string cannot contain a control
+ * character or an unescaped quote or backslash.
+ *
+ * @param string A String
+ * @return A String correctly formatted for insertion in a JSON text.
+ */
+ public static String quote(String string) {
+ StringWriter sw = new StringWriter();
+ synchronized (sw.getBuffer()) {
+ try {
+ return quote(string, sw).toString();
+ } catch (IOException ignored) {
+ // will never happen - we are writing to a string writer
+ return "";
+ }
+ }
+ }
+
+ public static Writer quote(String string, Writer w) throws IOException {
+ if (string == null || string.isEmpty()) {
+ w.write("\"\"");
+ return w;
+ }
+
+ char b;
+ char c = 0;
+ String hhhh;
+ int i;
+ int len = string.length();
+
+ w.write('"');
+ for (i = 0; i < len; i += 1) {
+ b = c;
+ c = string.charAt(i);
+ switch (c) {
+ case '\\':
+ case '"':
+ w.write('\\');
+ w.write(c);
+ break;
+ case '/':
+ if (b == '<') {
+ w.write('\\');
+ }
+ w.write(c);
+ break;
+ case '\b':
+ w.write("\\b");
+ break;
+ case '\t':
+ w.write("\\t");
+ break;
+ case '\n':
+ w.write("\\n");
+ break;
+ case '\f':
+ w.write("\\f");
+ break;
+ case '\r':
+ w.write("\\r");
+ break;
+ default:
+ if (c < ' ' || (c >= '\u0080' && c < '\u00a0') || (c >= '\u2000' && c < '\u2100')) {
+ w.write("\\u");
+ hhhh = Integer.toHexString(c);
+ w.write("0000", 0, 4 - hhhh.length());
+ w.write(hhhh);
+ } else {
+ w.write(c);
+ }
+ }
+ }
+ w.write('"');
+ return w;
+ }
+
+ /**
+ * Remove a name and its value, if present.
+ *
+ * @param key The name to be removed.
+ * @return The value that was associated with the name, or null if there was no
+ * value.
+ */
+ public Object remove(String key) {
+ return this.map.remove(key);
+ }
+
+ /**
+ * Determine if two JSONObjects are similar. They must contain the same set of
+ * names which must be associated with similar values.
+ *
+ * @param other The other JSONObject
+ * @return true if they are equal
+ */
+ public boolean similar(Object other) {
+ try {
+ if (!(other instanceof JSONObject)) {
+ return false;
+ }
+ if (!this.keySet().equals(((JSONObject) other).keySet())) {
+ return false;
+ }
+ for (final Entry<String, ?> entry : this.entrySet()) {
+ String name = entry.getKey();
+ Object valueThis = entry.getValue();
+ Object valueOther = ((JSONObject) other).get(name);
+ if (valueThis == valueOther) {
+ continue;
+ }
+ if (valueThis == null) {
+ return false;
+ }
+ if (valueThis instanceof JSONObject) {
+ if (!((JSONObject) valueThis).similar(valueOther)) {
+ return false;
+ }
+ } else if (valueThis instanceof JSONArray) {
+ if (!((JSONArray) valueThis).similar(valueOther)) {
+ return false;
+ }
+ } else if (!valueThis.equals(valueOther)) {
+ return false;
+ }
+ }
+ return true;
+ } catch (Throwable exception) {
+ return false;
+ }
+ }
+
+ /**
+ * Tests if the value should be tried as a decimal. It makes no test if there
+ * are actual digits.
+ *
+ * @param val value to test
+ * @return true if the string is "-0" or if it contains '.', 'e', or 'E', false
+ * otherwise.
+ */
+ protected static boolean isDecimalNotation(final String val) {
+ return val.indexOf('.') > -1 || val.indexOf('e') > -1 || val.indexOf('E') > -1 || "-0".equals(val);
+ }
+
+ /**
+ * Converts a string to a number using the narrowest possible type. Possible
+ * returns for this function are BigDecimal, Double, BigInteger, Long, and
+ * Integer. When a Double is returned, it should always be a valid Double and
+ * not NaN or +-infinity.
+ *
+ * @param val value to convert
+ * @return Number representation of the value.
+ * @throws NumberFormatException thrown if the value is not a valid number. A
+ * public caller should catch this and wrap it in
+ * a {@link JSONException} if applicable.
+ */
+ protected static Number stringToNumber(final String val) throws NumberFormatException {
+ char initial = val.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ // decimal representation
+ if (isDecimalNotation(val)) {
+ // quick dirty way to see if we need a BigDecimal instead of a Double
+ // this only handles some cases of overflow or underflow
+ if (val.length() > 14) {
+ return new BigDecimal(val);
+ }
+ final Double d = Double.valueOf(val);
+ if (d.isInfinite() || d.isNaN()) {
+ // if we can't parse it as a double, go up to BigDecimal
+ // this is probably due to underflow like 4.32e-678
+ // or overflow like 4.65e5324. The size of the string is small
+ // but can't be held in a Double.
+ return new BigDecimal(val);
+ }
+ return d;
+ }
+ // integer representation.
+ // This will narrow any values to the smallest reasonable Object representation
+ // (Integer, Long, or BigInteger)
+
+ // string version
+ // The compare string length method reduces GC,
+ // but leads to smaller integers being placed in larger wrappers even though not
+ // needed. i.e. 1,000,000,000 -> Long even though it's an Integer
+ // 1,000,000,000,000,000,000 -> BigInteger even though it's a Long
+ // if(val.length()<=9){
+ // return Integer.valueOf(val);
+ // }
+ // if(val.length()<=18){
+ // return Long.valueOf(val);
+ // }
+ // return new BigInteger(val);
+
+ // BigInteger version: We use a similar bitLenth compare as
+ // BigInteger#intValueExact uses. Increases GC, but objects hold
+ // only what they need. i.e. Less runtime overhead if the value is
+ // long lived. Which is the better tradeoff? This is closer to what's
+ // in stringToValue.
+ BigInteger bi = new BigInteger(val);
+ if (bi.bitLength() <= 31) {
+ return Integer.valueOf(bi.intValue());
+ }
+ if (bi.bitLength() <= 63) {
+ return Long.valueOf(bi.longValue());
+ }
+ return bi;
+ }
+ throw new NumberFormatException("val [" + val + "] is not a valid number.");
+ }
+
+ /**
+ * Try to convert a string into a number, boolean, or null. If the string can't
+ * be converted, return the string.
+ *
+ * @param string A String.
+ * @return A simple JSON value.
+ */
+ // Changes to this method must be copied to the corresponding method in
+ // the XML class to keep full support for Android
+ public static Object stringToValue(String string) {
+ if (string.equals("")) {
+ return string;
+ }
+ if (string.equalsIgnoreCase("true")) {
+ return Boolean.TRUE;
+ }
+ if (string.equalsIgnoreCase("false")) {
+ return Boolean.FALSE;
+ }
+ if (string.equalsIgnoreCase("null")) {
+ return JSONObject.NULL;
+ }
+
+ /*
+ * If it might be a number, try converting it. If a number cannot be produced,
+ * then the value will just be a string.
+ */
+
+ char initial = string.charAt(0);
+ if ((initial >= '0' && initial <= '9') || initial == '-') {
+ try {
+ // if we want full Big Number support this block can be replaced with:
+ // return stringToNumber(string);
+ if (isDecimalNotation(string)) {
+ Double d = Double.valueOf(string);
+ if (!d.isInfinite() && !d.isNaN()) {
+ return d;
+ }
+ } else {
+ Long myLong = Long.valueOf(string);
+ if (string.equals(myLong.toString())) {
+ if (myLong.longValue() == myLong.intValue()) {
+ return Integer.valueOf(myLong.intValue());
+ }
+ return myLong;
+ }
+ }
+ } catch (Exception ignore) {
+ }
+ }
+ return string;
+ }
+
+ /**
+ * Throw an exception if the object is a NaN or infinite number.
+ *
+ * @param o The object to test.
+ * @throws JSONException If o is a non-finite number.
+ */
+ public static void testValidity(Object o) throws JSONException {
+ if (o != null) {
+ if (o instanceof Double) {
+ if (((Double) o).isInfinite() || ((Double) o).isNaN()) {
+ throw new JSONException("JSON does not allow non-finite numbers.");
+ }
+ } else if (o instanceof Float) {
+ if (((Float) o).isInfinite() || ((Float) o).isNaN()) {
+ throw new JSONException("JSON does not allow non-finite numbers.");
+ }
+ }
+ }
+ }
+
+ /**
+ * Produce a JSONArray containing the values of the members of this JSONObject.
+ *
+ * @param names A JSONArray containing a list of key strings. This determines
+ * the sequence of the values in the result.
+ * @return A JSONArray of values.
+ * @throws JSONException If any of the values are non-finite numbers.
+ */
+ public JSONArray toJSONArray(JSONArray names) throws JSONException {
+ if (names == null || names.isEmpty()) {
+ return null;
+ }
+ JSONArray ja = new JSONArray();
+ for (int i = 0; i < names.length(); i += 1) {
+ ja.put(this.opt(names.getString(i)));
+ }
+ return ja;
+ }
+
+ /**
+ * Make a JSON text of this JSONObject. For compactness, no whitespace is added.
+ * If this would not result in a syntactically correct JSON text, then null will
+ * be returned instead.
+ * <p>
+ * <b> Warning: This method assumes that the data structure is acyclical. </b>
+ *
+ * @return a printable, displayable, portable, transmittable representation of
+ * the object, beginning with <code>{</code> <small>(left
+ * brace)</small> and ending with <code>}</code> <small>(right
+ * brace)</small>.
+ */
+ @Override
+ public String toString() {
+ try {
+ return this.toString(0);
+ } catch (Exception e) {
+ return null;
+ }
+ }
+
+ /**
+ * Make a pretty-printed JSON text of this JSONObject.
+ *
+ * <p>
+ * If <code>indentFactor > 0</code> and the {@link JSONObject} has only one key,
+ * then the object will be output on a single line:
+ *
+ * <pre>
+ * {@code {"key": 1}}
+ * </pre>
+ *
+ * <p>
+ * If an object has 2 or more keys, then it will be output across multiple
+ * lines: <code><pre>{
+ * "key1": 1,
+ * "key2": "value 2",
+ * "key3": 3
+ * }</pre></code>
+ * <p>
+ * <b> Warning: This method assumes that the data structure is acyclical. </b>
+ *
+ * @param indentFactor The number of spaces to add to each level of indentation.
+ * @return a printable, displayable, portable, transmittable representation of
+ * the object, beginning with <code>{</code> <small>(left
+ * brace)</small> and ending with <code>}</code> <small>(right
+ * brace)</small>.
+ * @throws JSONException If the object contains an invalid number.
+ */
+ public String toString(int indentFactor) throws JSONException {
+ StringWriter w = new StringWriter();
+ synchronized (w.getBuffer()) {
+ return this.write(w, indentFactor, 0).toString();
+ }
+ }
+
+ /**
+ * Make a JSON text of an Object value. If the object has an
+ * value.toJSONString() method, then that method will be used to produce the
+ * JSON text. The method is required to produce a strictly conforming text. If
+ * the object does not contain a toJSONString method (which is the most common
+ * case), then a text will be produced by other means. If the value is an array
+ * or Collection, then a JSONArray will be made from it and its toJSONString
+ * method will be called. If the value is a MAP, then a JSONObject will be made
+ * from it and its toJSONString method will be called. Otherwise, the value's
+ * toString method will be called, and the result will be quoted.
+ *
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @param value The value to be serialized.
+ * @return a printable, displayable, transmittable representation of the object,
+ * beginning with <code>{</code> <small>(left brace)</small> and
+ * ending with <code>}</code> <small>(right brace)</small>.
+ * @throws JSONException If the value is or contains an invalid number.
+ */
+ public static String valueToString(Object value) throws JSONException {
+ // moves the implementation to JSONWriter as:
+ // 1. It makes more sense to be part of the writer class
+ // 2. For Android support this method is not available. By implementing it in
+ // the Writer
+ // Android users can use the writer with the built in Android JSONObject
+ // implementation.
+ return JSONWriter.valueToString(value);
+ }
+
+ /**
+ * Wrap an object, if necessary. If the object is <code>null</code>, return the
+ * NULL object. If it is an array or collection, wrap it in a JSONArray. If it
+ * is a map, wrap it in a JSONObject. If it is a standard property (Double,
+ * String, et al) then it is already wrapped. Otherwise, if it comes from one of
+ * the java packages, turn it into a string. And if it doesn't, try to wrap it
+ * in a JSONObject. If the wrapping fails, then null is returned.
+ *
+ * @param object The object to wrap
+ * @return The wrapped value
+ */
+ public static Object wrap(Object object) {
+ try {
+ if (object == null) {
+ return NULL;
+ }
+ if (object instanceof JSONObject || object instanceof JSONArray || NULL.equals(object)
+ || object instanceof JSONString || object instanceof Byte || object instanceof Character
+ || object instanceof Short || object instanceof Integer || object instanceof Long
+ || object instanceof Boolean || object instanceof Float || object instanceof Double
+ || object instanceof String || object instanceof BigInteger || object instanceof BigDecimal
+ || object instanceof Enum) {
+ return object;
+ }
+
+ if (object instanceof Collection) {
+ Collection<?> coll = (Collection<?>) object;
+ return new JSONArray(coll);
+ }
+ if (object.getClass().isArray()) {
+ return new JSONArray(object);
+ }
+ if (object instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) object;
+ return new JSONObject(map);
+ }
+ Package objectPackage = object.getClass().getPackage();
+ String objectPackageName = objectPackage != null ? objectPackage.getName() : "";
+ if (objectPackageName.startsWith("java.") || objectPackageName.startsWith("javax.")
+ || object.getClass().getClassLoader() == null) {
+ return object.toString();
+ }
+ return new JSONObject(object);
+ } catch (Exception exception) {
+ return null;
+ }
+ }
+
+ /**
+ * Write the contents of the JSONObject as JSON text to a writer. For
+ * compactness, no whitespace is added.
+ * <p>
+ * <b> Warning: This method assumes that the data structure is acyclical. </b>
+ *
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer) throws JSONException {
+ return this.write(writer, 0, 0);
+ }
+
+ static final Writer writeValue(Writer writer, Object value, int indentFactor, int indent)
+ throws JSONException, IOException {
+ if (value == null || value.equals(null)) {
+ writer.write("null");
+ } else if (value instanceof JSONString) {
+ Object o;
+ try {
+ o = ((JSONString) value).toJSONString();
+ } catch (Exception e) {
+ throw new JSONException(e);
+ }
+ writer.write(o != null ? o.toString() : quote(value.toString()));
+ } else if (value instanceof Number) {
+ // not all Numbers may match actual JSON Numbers. i.e. fractions or Imaginary
+ final String numberAsString = numberToString((Number) value);
+ try {
+ // Use the BigDecimal constructor for its parser to validate the format.
+ @SuppressWarnings("unused")
+ BigDecimal testNum = new BigDecimal(numberAsString);
+ // Close enough to a JSON number that we will use it unquoted
+ writer.write(numberAsString);
+ } catch (NumberFormatException ex) {
+ // The Number value is not a valid JSON number.
+ // Instead we will quote it as a string
+ quote(numberAsString, writer);
+ }
+ } else if (value instanceof Boolean) {
+ writer.write(value.toString());
+ } else if (value instanceof Enum<?>) {
+ writer.write(quote(((Enum<?>) value).name()));
+ } else if (value instanceof JSONObject) {
+ ((JSONObject) value).write(writer, indentFactor, indent);
+ } else if (value instanceof JSONArray) {
+ ((JSONArray) value).write(writer, indentFactor, indent);
+ } else if (value instanceof Map) {
+ Map<?, ?> map = (Map<?, ?>) value;
+ new JSONObject(map).write(writer, indentFactor, indent);
+ } else if (value instanceof Collection) {
+ Collection<?> coll = (Collection<?>) value;
+ new JSONArray(coll).write(writer, indentFactor, indent);
+ } else if (value.getClass().isArray()) {
+ new JSONArray(value).write(writer, indentFactor, indent);
+ } else {
+ quote(value.toString(), writer);
+ }
+ return writer;
+ }
+
+ static final void indent(Writer writer, int indent) throws IOException {
+ for (int i = 0; i < indent; i += 1) {
+ writer.write(' ');
+ }
+ }
+
+ /**
+ * Write the contents of the JSONObject as JSON text to a writer.
+ *
+ * <p>
+ * If <code>indentFactor > 0</code> and the {@link JSONObject} has only one key,
+ * then the object will be output on a single line:
+ *
+ * <pre>
+ * {@code {"key": 1}}
+ * </pre>
+ *
+ * <p>
+ * If an object has 2 or more keys, then it will be output across multiple
+ * lines: <code><pre>{
+ * "key1": 1,
+ * "key2": "value 2",
+ * "key3": 3
+ * }</pre></code>
+ * <p>
+ * <b> Warning: This method assumes that the data structure is acyclical. </b>
+ *
+ * @param writer Writes the serialized JSON
+ * @param indentFactor The number of spaces to add to each level of indentation.
+ * @param indent The indentation of the top level.
+ * @return The writer.
+ * @throws JSONException
+ */
+ public Writer write(Writer writer, int indentFactor, int indent) throws JSONException {
+ try {
+ boolean commanate = false;
+ final int length = this.length();
+ writer.write('{');
+
+ if (length == 1) {
+ final Entry<String, ?> entry = this.entrySet().iterator().next();
+ final String key = entry.getKey();
+ writer.write(quote(key));
+ writer.write(':');
+ if (indentFactor > 0) {
+ writer.write(' ');
+ }
+ try {
+ writeValue(writer, entry.getValue(), indentFactor, indent);
+ } catch (Exception e) {
+ throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+ }
+ } else if (length != 0) {
+ final int newindent = indent + indentFactor;
+ for (final Entry<String, ?> entry : this.entrySet()) {
+ if (commanate) {
+ writer.write(',');
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, newindent);
+ final String key = entry.getKey();
+ writer.write(quote(key));
+ writer.write(':');
+ if (indentFactor > 0) {
+ writer.write(' ');
+ }
+ try {
+ writeValue(writer, entry.getValue(), indentFactor, newindent);
+ } catch (Exception e) {
+ throw new JSONException("Unable to write JSONObject value for key: " + key, e);
+ }
+ commanate = true;
+ }
+ if (indentFactor > 0) {
+ writer.write('\n');
+ }
+ indent(writer, indent);
+ }
+ writer.write('}');
+ return writer;
+ } catch (IOException exception) {
+ throw new JSONException(exception);
+ }
+ }
+
+ /**
+ * Returns a java.util.Map containing all of the entries in this object. If an
+ * entry in the object is a JSONArray or JSONObject it will also be converted.
+ * <p>
+ * Warning: This method assumes that the data structure is acyclical.
+ *
+ * @return a java.util.Map containing the entries of this object
+ */
+ public Map<String, Object> toMap() {
+ Map<String, Object> results = new HashMap<String, Object>();
+ for (Entry<String, Object> entry : this.entrySet()) {
+ Object value;
+ if (entry.getValue() == null || NULL.equals(entry.getValue())) {
+ value = null;
+ } else if (entry.getValue() instanceof JSONObject) {
+ value = ((JSONObject) entry.getValue()).toMap();
+ } else if (entry.getValue() instanceof JSONArray) {
+ value = ((JSONArray) entry.getValue()).toList();
+ } else {
+ value = entry.getValue();
+ }
+ results.put(entry.getKey(), value);
+ }
+ return results;
+ }
+}
import jalview.datamodel.AlignmentAnnotation;
import jalview.datamodel.Annotation;
+ import jalview.datamodel.HiddenMarkovModel;
import jalview.datamodel.Profile;
import jalview.datamodel.ProfileI;
+ import jalview.datamodel.Profiles;
import jalview.datamodel.ProfilesI;
+import jalview.datamodel.ResidueCount;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
+ import jalview.io.DataSourceType;
+ import jalview.io.FileParse;
+ import jalview.io.HMMFile;
+
+ import java.io.IOException;
+ import java.net.MalformedURLException;
+import java.util.Hashtable;
+
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
assertEquals("T", ann.displayCharacter);
}
+ /**
+ * Test to include rounding down of a non-zero count to 0% (JAL-3202)
+ */
+ @Test(groups = { "Functional" })
+ public void testExtractProfile()
+ {
+ /*
+ * 200 sequences of which 30 gapped (170 ungapped)
+ * max count 70 for modal residue 'G'
+ */
+ ProfileI profile = new Profile(200, 30, 70, "G");
+ ResidueCount counts = new ResidueCount();
+ counts.put('G', 70);
+ counts.put('R', 60);
+ counts.put('L', 38);
+ counts.put('H', 2);
+ profile.setCounts(counts);
+
+ /*
+ * [0, noOfValues, totalPercent, char1, count1, ...]
+ * G: 70/170 = 41.2 = 41
+ * R: 60/170 = 35.3 = 35
+ * L: 38/170 = 22.3 = 22
+ * H: 2/170 = 1
+ * total (rounded) percentages = 99
+ */
+ int[] extracted = AAFrequency.extractProfile(profile, true);
+ int[] expected = new int[] { 0, 4, 99, 'G', 41, 'R', 35, 'L', 22, 'H',
+ 1 };
+ org.testng.Assert.assertEquals(extracted, expected);
+
+ /*
+ * add some counts of 1; these round down to 0% and should be discarded
+ */
+ counts.put('G', 68); // 68/170 = 40% exactly (percentages now total 98)
+ counts.put('Q', 1);
+ counts.put('K', 1);
+ extracted = AAFrequency.extractProfile(profile, true);
+ expected = new int[] { 0, 4, 98, 'G', 40, 'R', 35, 'L', 22, 'H', 1 };
+ org.testng.Assert.assertEquals(extracted, expected);
+
+ }
+
+ /**
+ * Tests for the profile calculation where gaps are included i.e. the
+ * denominator is the total number of sequences in the column
+ */
+ @Test(groups = { "Functional" })
+ public void testExtractProfile_countGaps()
+ {
+ /*
+ * 200 sequences of which 30 gapped (170 ungapped)
+ * max count 70 for modal residue 'G'
+ */
+ ProfileI profile = new Profile(200, 30, 70, "G");
+ ResidueCount counts = new ResidueCount();
+ counts.put('G', 70);
+ counts.put('R', 60);
+ counts.put('L', 38);
+ counts.put('H', 2);
+ profile.setCounts(counts);
+
+ /*
+ * [0, noOfValues, totalPercent, char1, count1, ...]
+ * G: 70/200 = 35%
+ * R: 60/200 = 30%
+ * L: 38/200 = 19%
+ * H: 2/200 = 1%
+ * total (rounded) percentages = 85
+ */
+ int[] extracted = AAFrequency.extractProfile(profile, false);
+ int[] expected = new int[] { AlignmentAnnotation.SEQUENCE_PROFILE, 4,
+ 85, 'G', 35, 'R', 30, 'L', 19, 'H',
+ 1 };
+ org.testng.Assert.assertEquals(extracted, expected);
+
+ /*
+ * add some counts of 1; these round down to 0% and should be discarded
+ */
+ counts.put('G', 68); // 68/200 = 34%
+ counts.put('Q', 1);
+ counts.put('K', 1);
+ extracted = AAFrequency.extractProfile(profile, false);
+ expected = new int[] { AlignmentAnnotation.SEQUENCE_PROFILE, 4, 84, 'G',
+ 34, 'R', 30, 'L', 19, 'H', 1 };
+ org.testng.Assert.assertEquals(extracted, expected);
+
+ }
+
+ @Test(groups = { "Functional" })
+ public void testExtractCdnaProfile()
+ {
+ /*
+ * 200 sequences of which 30 gapped (170 ungapped)
+ * max count 70 for modal residue 'G'
+ */
+ Hashtable profile = new Hashtable();
+
+ /*
+ * cdna profile is {seqCount, ungappedCount, codonCount1, ...codonCount64}
+ * where 1..64 positions correspond to encoded codons
+ * see CodingUtils.encodeCodon()
+ */
+ int[] codonCounts = new int[66];
+ char[] codon1 = new char[] { 'G', 'C', 'A' };
+ char[] codon2 = new char[] { 'c', 'C', 'A' };
+ char[] codon3 = new char[] { 't', 'g', 'A' };
+ char[] codon4 = new char[] { 'G', 'C', 't' };
+ int encoded1 = CodingUtils.encodeCodon(codon1);
+ int encoded2 = CodingUtils.encodeCodon(codon2);
+ int encoded3 = CodingUtils.encodeCodon(codon3);
+ int encoded4 = CodingUtils.encodeCodon(codon4);
+ codonCounts[2 + encoded1] = 30;
+ codonCounts[2 + encoded2] = 70;
+ codonCounts[2 + encoded3] = 9;
+ codonCounts[2 + encoded4] = 1;
+ codonCounts[0] = 120;
+ codonCounts[1] = 110;
+ profile.put(AAFrequency.PROFILE, codonCounts);
+
+ /*
+ * [0, noOfValues, totalPercent, char1, count1, ...]
+ * codon1: 30/110 = 27.2 = 27%
+ * codon2: 70/110 = 63.6% = 63%
+ * codon3: 9/110 = 8.1% = 8%
+ * codon4: 1/110 = 0.9% = 0% should be discarded
+ * total (rounded) percentages = 98
+ */
+ int[] extracted = AAFrequency.extractCdnaProfile(profile, true);
+ int[] expected = new int[] { AlignmentAnnotation.CDNA_PROFILE, 3, 98,
+ encoded2, 63, encoded1, 27, encoded3, 8 };
+ org.testng.Assert.assertEquals(extracted, expected);
+ }
+
+ @Test(groups = { "Functional" })
+ public void testExtractCdnaProfile_countGaps()
+ {
+ /*
+ * 200 sequences of which 30 gapped (170 ungapped)
+ * max count 70 for modal residue 'G'
+ */
+ Hashtable profile = new Hashtable();
+
+ /*
+ * cdna profile is {seqCount, ungappedCount, codonCount1, ...codonCount64}
+ * where 1..64 positions correspond to encoded codons
+ * see CodingUtils.encodeCodon()
+ */
+ int[] codonCounts = new int[66];
+ char[] codon1 = new char[] { 'G', 'C', 'A' };
+ char[] codon2 = new char[] { 'c', 'C', 'A' };
+ char[] codon3 = new char[] { 't', 'g', 'A' };
+ char[] codon4 = new char[] { 'G', 'C', 't' };
+ int encoded1 = CodingUtils.encodeCodon(codon1);
+ int encoded2 = CodingUtils.encodeCodon(codon2);
+ int encoded3 = CodingUtils.encodeCodon(codon3);
+ int encoded4 = CodingUtils.encodeCodon(codon4);
+ codonCounts[2 + encoded1] = 30;
+ codonCounts[2 + encoded2] = 70;
+ codonCounts[2 + encoded3] = 9;
+ codonCounts[2 + encoded4] = 1;
+ codonCounts[0] = 120;
+ codonCounts[1] = 110;
+ profile.put(AAFrequency.PROFILE, codonCounts);
+
+ /*
+ * [0, noOfValues, totalPercent, char1, count1, ...]
+ * codon1: 30/120 = 25%
+ * codon2: 70/120 = 58.3 = 58%
+ * codon3: 9/120 = 7.5 = 7%
+ * codon4: 1/120 = 0.8 = 0% should be discarded
+ * total (rounded) percentages = 90
+ */
+ int[] extracted = AAFrequency.extractCdnaProfile(profile, false);
+ int[] expected = new int[] { AlignmentAnnotation.CDNA_PROFILE, 3, 90,
+ encoded2, 58, encoded1, 25, encoded3, 7 };
+ org.testng.Assert.assertEquals(extracted, expected);
+ }
++
+ @Test(groups = { "Functional" })
+ public void testExtractHMMProfile()
+ throws MalformedURLException, IOException
+ {
+ int[] expected = { 0, 4, 100, 'T', 71, 'C', 12, 'G', 9, 'A', 9 };
+ int[] actual = AAFrequency.extractHMMProfile(hmm, 17, false, false);
+ for (int i = 0; i < actual.length; i++)
+ {
+ if (i == 2)
+ {
+ assertEquals(actual[i], expected[i]);
+ }
+ else
+ {
+ assertEquals(actual[i], expected[i]);
+ }
+ }
+
+ int[] expected2 = { 0, 4, 100, 'A', 85, 'C', 0, 'G', 0, 'T', 0 };
+ int[] actual2 = AAFrequency.extractHMMProfile(hmm, 2, true, false);
+ for (int i = 0; i < actual2.length; i++)
+ {
+ if (i == 2)
+ {
+ assertEquals(actual[i], expected[i]);
+ }
+ else
+ {
+ assertEquals(actual[i], expected[i]);
+ }
+ }
+
+ assertNull(AAFrequency.extractHMMProfile(null, 98978867, true, false));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetAnalogueCount()
+ {
+ /*
+ * 'T' in column 0 has emission probability 0.7859, scales to 7859
+ */
+ int count = AAFrequency.getAnalogueCount(hmm, 0, 'T', false, false);
+ assertEquals(7859, count);
+
+ /*
+ * same with 'use info height': value is multiplied by log ratio
+ * log(value / background) / log(2) = log(0.7859/0.25)/0.693
+ * = log(3.1)/0.693 = 1.145/0.693 = 1.66
+ * so value becomes 1.2987 and scales up to 12987
+ */
+ count = AAFrequency.getAnalogueCount(hmm, 0, 'T', false, true);
+ assertEquals(12987, count);
+
+ /*
+ * 'G' in column 20 has emission probability 0.75457, scales to 7546
+ */
+ count = AAFrequency.getAnalogueCount(hmm, 20, 'G', false, false);
+ assertEquals(7546, count);
+
+ /*
+ * 'G' in column 1077 has emission probability 0.0533, here
+ * ignored (set to 0) since below background of 0.25
+ */
+ count = AAFrequency.getAnalogueCount(hmm, 1077, 'G', true, false);
+ assertEquals(0, count);
+ }
+
+ @Test(groups = { "Functional" })
+ public void testCompleteInformation()
+ {
+ ProfileI prof1 = new Profile(1, 0, 100, "A");
+ ProfileI prof2 = new Profile(1, 0, 100, "-");
+
+ ProfilesI profs = new Profiles(new ProfileI[] { prof1, prof2 });
+ Annotation ann1 = new Annotation(6.5f);
+ Annotation ann2 = new Annotation(0f);
+ Annotation[] annots = new Annotation[] { ann1, ann2 };
+ SequenceI seq = new Sequence("", "AA", 0, 0);
+ seq.setHMM(hmm);
+ AlignmentAnnotation annot = new AlignmentAnnotation("", "", annots);
+ annot.setSequenceRef(seq);
+ AAFrequency.completeInformation(annot, profs, 0, 1);
+ float ic = annot.annotations[0].value;
+ assertEquals(0.91532f, ic, 0.0001f);
+ ic = annot.annotations[1].value;
+ assertEquals(0f, ic, 0.0001f);
+ int i = 0;
+ }
-
}
import jalview.util.MapList;
import jalview.ws.SequenceFetcher;
import jalview.ws.SequenceFetcherFactory;
- import jalview.ws.params.InvalidArgumentException;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import org.testng.annotations.AfterClass;
import jalview.gui.JvOptionPane;
--import javax.swing.JComboBox;
import javax.swing.JInternalFrame;
import org.testng.annotations.AfterMethod;
import static org.testng.Assert.assertSame;
import static org.testng.Assert.assertTrue;
+import java.awt.Color;
+import java.util.Iterator;
+
+import org.testng.annotations.AfterMethod;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Test;
+
+ import jalview.api.AlignViewportI;
import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.bin.Jalview;
import jalview.schemes.StrandColourScheme;
import jalview.schemes.TurnColourScheme;
import jalview.util.MessageManager;
+ import jalview.viewmodel.AlignmentViewport;
-import java.awt.Color;
-import java.util.Iterator;
-
-import org.testng.annotations.AfterMethod;
-import org.testng.annotations.BeforeClass;
-import org.testng.annotations.BeforeMethod;
-import org.testng.annotations.Test;
-
public class AlignFrameTest
{
AlignFrame af;
import static org.testng.Assert.assertEquals;
+import java.awt.Color;
+
+import org.testng.annotations.Test;
+
import jalview.datamodel.SequenceI;
import jalview.gui.AlignFrame;
- import jalview.gui.AlignViewport;
import jalview.io.DataSourceType;
import jalview.io.FileLoader;
+ import jalview.viewmodel.AlignmentViewport;
-import java.awt.Color;
-
-import org.testng.annotations.Test;
-
public class PIDColourSchemeTest
{
static final Color white = Color.white;
*/
AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqs,
DataSourceType.PASTE);
- AlignViewport viewport = af.getViewport();
+ AlignmentViewport viewport = af.getViewport();
viewport.setIgnoreGapsConsensus(false, af.alignPanel);
- while (viewport.getConsensusSeq() == null)
+ do
{
- synchronized (this)
+ try
+ {
+ Thread.sleep(50);
+ } catch (InterruptedException x)
{
- try
- {
- wait(50);
- } catch (InterruptedException e)
- {
- }
}
- }
+ } while (af.getViewport().getCalcManager().isWorking());
af.changeColour_actionPerformed(JalviewColourScheme.PID.toString());
SequenceI seq = viewport.getAlignment().getSequenceAt(0);