import java.util.Properties;
import java.util.StringTokenizer;
import java.util.TreeSet;
+import java.util.regex.Pattern;
-import org.apache.log4j.ConsoleAppender;
-import org.apache.log4j.Level;
-import org.apache.log4j.Logger;
-import org.apache.log4j.SimpleLayout;
+import javax.swing.LookAndFeel;
+import javax.swing.UIManager;
-
import jalview.bin.ApplicationSingletonProvider.ApplicationSingletonI;
import jalview.datamodel.PDBEntry;
+import jalview.gui.Preferences;
import jalview.gui.UserDefinedColours;
+import jalview.log.JLoggerLog4j;
import jalview.schemes.ColourSchemeLoader;
import jalview.schemes.ColourSchemes;
import jalview.schemes.UserColourScheme;
private void loadPropertiesImpl(String propsFile)
{
+
propertiesFile = propsFile;
+ String releasePropertiesFile = null;
+ boolean defaultProperties = false;
if (propsFile == null && !propsAreReadOnly)
{
- propertiesFile = Platform.getUserPath(".jalview_properties");
+ // TODO: @bsoares - for 2.12 testing: check test,develop,release props are located correctly
+ String channelPrefsFilename = ChannelProperties
+ .getProperty("preferences.filename");
+ String releasePrefsFilename = ".jalview_properties";
+ propertiesFile = Platform.getUserPath(channelPrefsFilename);
+ releasePropertiesFile = Platform.getUserPath(releasePrefsFilename);
+ defaultProperties = true;
}
else
{
System.out.println("Error reading properties file: " + ex);
}
}
-
+ /* TO BE REPLACED WITH PROXY_TYPE SETTINGS
if (getDefault("USE_PROXY", false))
{
String proxyServer = getDefault("PROXY_SERVER", ""),
&& (System.getProperty("java.awt.headless") == null || System
.getProperty("java.awt.headless").equals("false")))
{
-
- new Thread()
+ class VersionChecker extends Thread
{
@Override
public void run()
{
+ String remoteBuildPropertiesUrl = Cache
+ .getAppbaseBuildProperties();
-
String orgtimeout = System
.getProperty("sun.net.client.defaultConnectTimeout");
if (orgtimeout == null)
{
// consider returning more human friendly info
// eg 'built from Source' or update channel
- return jalview.bin.Cache.getDefault("INSTALLATION", "unknown");
+ return Cache.getDefault("INSTALLATION", "unknown");
}
-
/**
*
* For AppletParams and Preferences ok_actionPerformed and
import jalview.ws.params.ParamDatastoreI;
import jalview.ws.params.WsParamSetI;
import jalview.ws.seqfetcher.DbSourceProxy;
- import jalview.ws.slivkaws.SlivkaWSDiscoverer;
+ import jalview.ws2.WebServiceDiscoverer;
+ import jalview.ws2.WebServiceI;
+ import jalview.ws2.operations.Operation;
+ import jalview.ws2.slivka.SlivkaWSDiscoverer;
-
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
private int id;
private DataSourceType protocol ;
+
/**
* Creates a new AlignFrame object with specific width and height.
- *
+ *
* @param al
* @param width
* @param height
/**
* JavaScript will have this, maybe others. More dependable than a file name
* and maintains a reference to the actual bytes loaded.
- *
+ *
* @param file
*/
+
public void setFileObject(File file)
{
this.fileObject = file;
{
buildWebServicesMenu();
}
+
+ @Override
+ public void servicesChanged(WebServiceDiscoverer discoverer,
+ Collection<? extends WebServiceI> services)
+ {
+ buildWebServicesMenu();
+ }
-
/* Set up intrinsic listeners for dynamically generated GUI bits. */
private void addServiceListeners()
{
if (Cache.getDefault("SHOW_SLIVKA_SERVICES", true))
{
- WSDiscovererI discoverer = SlivkaWSDiscoverer.getInstance();
- WebServiceDiscoverer discoverer = SlivkaWSDiscoverer.getInstance();
- discoverer.addServiceChangeListener((disc, srvcs) -> buildWebServicesMenu());
++ SlivkaWSDiscoverer discoverer = SlivkaWSDiscoverer.getInstance();
+ discoverer.addServiceChangeListener(this);
}
if (Cache.getDefault("SHOW_JWS2_SERVICES", true))
{
/**
* Set the enabled state of the 'Run Groovy' option in the Calculate menu
- *
+ *
* @param b
*/
+
public void setGroovyEnabled(boolean b)
{
runGroovy.setEnabled(b);
/*
* (non-Javadoc)
- *
+ *
* @see jalview.gui.IProgressIndicator#setProgressBar(java.lang.String, long)
*/
+
@Override
public void setProgressBar(String message, long id)
{
}
/**
- *
+ *
* @return true if any progress bars are still active
*/
+
@Override
public boolean operationInProgress()
{
/*
* Added so Castor Mapping file can obtain Jalview Version
*/
+
public String getVersion()
{
- return jalview.bin.Cache.getProperty("VERSION");
+ return Cache.getProperty("VERSION");
}
public FeatureRenderer getFeatureRenderer()
{
String tempFilePath = doBackup ? backupfiles.getTempFilePath()
: file;
+ Console.trace("ALIGNFRAME setting PrintWriter");
PrintWriter out = new PrintWriter(new FileWriter(tempFilePath));
+ if (backupfiles != null)
+ {
+ Console.trace("ALIGNFRAME about to write to temp file "
+ + backupfiles.getTempFilePath());
+ }
-
out.print(output);
+ Console.trace("ALIGNFRAME about to close file");
out.close();
+ Console.trace("ALIGNFRAME closed file");
AlignFrame.this.setTitle(file);
statusBar.setText(MessageManager.formatMessage(
"label.successfully_saved_to_file_in_format",
* Outputs the alignment to textbox in the requested format, if necessary
* first prompting the user for whether to include hidden regions or
* non-sequence data
- *
+ *
* @param fileFormatName
*/
+
@Override
protected void outputText_actionPerformed(String fileFormatName)
{
/**
* Creates a PNG image of the alignment and writes it to the given file. If
* the file is null, the user is prompted to choose a file.
- *
+ *
* @param f
*/
+
@Override
public void createPNG(File f)
{
/**
* Creates an EPS image of the alignment and writes it to the given file. If
* the file is null, the user is prompted to choose a file.
- *
+ *
* @param f
*/
+
@Override
public void createEPS(File f)
{
/**
* Creates an SVG image of the alignment and writes it to the given file. If
* the file is null, the user is prompted to choose a file.
- *
+ *
* @param f
*/
+
@Override
public void createSVG(File f)
{
/**
* Close the current view or all views in the alignment frame. If the frame
* only contains one view then the alignment will be removed from memory.
- *
+ *
* @param closeAllTabs
*/
+
@Override
public void closeMenuItem_actionPerformed(boolean closeAllTabs)
{
/**
* Close the specified panel and close up tabs appropriately.
- *
+ *
* @param panelToClose
*/
+
public void closeView(AlignmentPanel panelToClose)
{
int index = tabbedPane.getSelectedIndex();
}
/**
- *
+ *
* @return alignment objects for all views
*/
+
AlignmentI[] getViewAlignments()
{
if (alignPanels != null)
}
/**
- * DOCUMENT ME!
- *
+ * Calls AlignmentI.moveSelectedSequencesByOne with current sequence selection
+ * or the sequence under cursor in keyboard mode
+ *
* @param up
- * DOCUMENT ME!
+ * or down (if !up)
*/
+
public void moveSelectedSequences(boolean up)
{
SequenceGroup sg = viewport.getSelectionGroup();
if (sg == null)
{
+ if (viewport.cursorMode)
+ {
+ sg = new SequenceGroup();
+ sg.addSequence(viewport.getAlignment().getSequenceAt(
+ alignPanel.getSeqPanel().seqCanvas.cursorY), false);
+ }
+ else
+ {
+ return;
+ }
+ }
+
+ if (sg.getSize() < 1)
+ {
return;
}
-
+ // TODO: JAL-3733 - add an event to the undo buffer for this !
-
viewport.getAlignment().moveSelectedSequencesByOne(sg,
viewport.getHiddenRepSequences(), up);
alignPanel.paintAlignment(true, false);
}
/**
- * DOCUMENT ME!
- *
+ * Opens a Finder dialog
+ *
* @param e
- * DOCUMENT ME!
*/
+
@Override
public void findMenuItem_actionPerformed(ActionEvent e)
{
/*
* (non-Javadoc)
- *
+ *
* @see jalview.jbgui.GAlignFrame#followHighlight_actionPerformed()
*/
+
@Override
protected void followHighlight_actionPerformed()
{
/**
* Action on toggle of the 'Show annotations' menu item. This shows or hides
* the annotations panel as a whole.
- *
+ *
* The options to show/hide all annotations should be enabled when the panel
* is shown, and disabled when the panel is hidden.
- *
+ *
* @param e
*/
+
@Override
public void annotationPanelMenuItem_actionPerformed(ActionEvent e)
{
* Action on the user checking or unchecking the option to apply the selected
* colour scheme to all groups. If unchecked, groups may have their own
* independent colour schemes.
- *
+ *
* @param selected
*/
+
@Override
public void applyToAllGroups_actionPerformed(boolean selected)
{
/**
* Actions on setting or changing the alignment colour scheme
- *
+ *
* @param cs
*/
+
@Override
public void changeColour(ColourSchemeI cs)
{
alignPanel.paintAlignment(true, false);
}
+
/**
* DOCUMENT ME!
- *
+ *
* @param e
* DOCUMENT ME!
*/
* search the alignment and rebuild the sort by annotation score submenu the
* last alignment annotation vector hash is stored to minimize cost of
* rebuilding in subsequence calls.
- *
+ *
*/
+
@Override
public void buildSortByAnnotationScoresMenu()
{
/**
* Work out whether the whole set of sequences or just the selected set will
* be submitted for multiple alignment.
- *
+ *
*/
+
public jalview.datamodel.AlignmentView gatherSequencesForAlignment()
{
// Now, check we have enough sequences
}
}
+ private void buildWebServicesMenu(WebServiceDiscoverer discoverer, final JMenu menu)
+ {
+ if (discoverer.hasServices())
+ {
+ var builder = new WebServicesMenuBuilder();
+ for (var service : discoverer.getServices())
+ builder.addAllOperations(service.getOperations());
+ builder.addSelectedHostChangeListener((name, op) -> {
+ menu.removeAll();
+ builder.buildMenu(menu, this);
+ });
+ builder.buildMenu(menu, this);
+ }
+ if (discoverer.isRunning())
+ {
+ JMenuItem item = new JMenuItem("Service discovery in progress.");
+ item.setEnabled(false);
+ menu.add(item);
+ }
+ else if (!discoverer.hasServices())
+ {
+ JMenuItem item = new JMenuItem("No services available.");
+ item.setEnabled(false);
+ menu.add(item);
+ }
+ }
-
/**
* construct any groupURL type service menu entries.
- *
+ *
* @param webService
*/
+
protected void build_urlServiceMenu(JMenu webService)
{
// TODO: remove this code when 2.7 is released
* Cross-References menu (formerly called Show Products), with database
* sources for which cross-references are found (protein sources for a
* nucleotide alignment and vice versa)
- *
+ *
* @return true if Show Cross-references menu should be enabled
*/
+
public boolean canShowProducts()
{
SequenceI[] seqs = viewport.getAlignment().getSequencesArray();
/**
* Set the file format
- *
+ *
* @param format
*/
+
public void setFileFormat(FileFormatI format)
{
this.currentFileFormat = format;
/**
* find the viewport amongst the tabs in this alignment frame and close that
* tab
- *
+ *
* @param av
*/
+
public boolean closeView(AlignViewportI av)
{
if (viewport == av)
/**
* make the given alignmentPanel the currently selected tab
- *
+ *
* @param alignmentPanel
*/
+
public void setDisplayedView(AlignmentPanel alignmentPanel)
{
if (!viewport.getSequenceSetId()
}
/**
- *
+ *
* @return alignment panels in this alignment frame
*/
+
public List<? extends AlignmentViewPanel> getAlignPanels()
{
// alignPanels is never null
/**
* Set visibility of dna/protein complement view (available when shown in a
* split frame).
- *
+ *
* @param show
*/
+
@Override
protected void showComplement_actionPerformed(boolean show)
{
}
}
}
-
}
+
@SuppressWarnings("deprecation")
private JalviewChangeSupport changeSupport = new JalviewChangeSupport();
+ public static boolean nosplash = false;
-
/**
* news reader - null if it was never started.
*/
*/
doConfigureStructurePrefs();
- setTitle("Jalview " + Cache.getProperty("VERSION"));
- /*
- if (!Platform.isAMac())
- {
- // this.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
- }
- else
+ setTitle(ChannelProperties.getProperty("app_name") + " " + Cache.getProperty("VERSION"));
+
+ /**
+ * Set taskbar "grouped windows" name for linux desktops (works in GNOME and
+ * KDE). This uses sun.awt.X11.XToolkit.awtAppClassName which is not officially
+ * documented or guaranteed to exist, so we access it via reflection. There
+ * appear to be unfathomable criteria about what this string can contain, and it
+ * if doesn't meet those criteria then "java" (KDE) or "jalview-bin-Jalview"
+ * (GNOME) is used. "Jalview", "Jalview Develop" and "Jalview Test" seem okay,
+ * but "Jalview non-release" does not. The reflection access may generate a
+ * warning: WARNING: An illegal reflective access operation has occurred
+ * WARNING: Illegal reflective access by jalview.gui.Desktop () to field
+ * sun.awt.X11.XToolkit.awtAppClassName which I don't think can be avoided.
+ */
+ if (Platform.isLinux())
+ {
+ if (LaunchUtils.getJavaVersion() >= 11)
{
- this.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE);
+ jalview.bin.Console.info(
+ "Linux platform only! You may have the following warning next: \"WARNING: An illegal reflective access operation has occurred\"\nThis is expected and cannot be avoided, sorry about that.");
}
- */
+
try
{
- APQHandlers.setAPQHandlers(this);
- } catch (Throwable t)
- {
- System.out.println("Error setting APQHandlers: " + t.toString());
- // t.printStackTrace();
- }
+ Toolkit xToolkit = Toolkit.getDefaultToolkit();
+ Field[] declaredFields = xToolkit.getClass().getDeclaredFields();
+ Field awtAppClassNameField = null;
- addWindowListener(new WindowAdapter()
- {
+ if (Arrays.stream(declaredFields).anyMatch(f -> f.getName().equals("awtAppClassName"))) {
+ awtAppClassNameField = xToolkit.getClass().getDeclaredField("awtAppClassName");
+ }
- @Override
- public void windowClosing(WindowEvent ev)
+ String title = ChannelProperties.getProperty("app_name");
+ if (awtAppClassNameField != null) {
+ awtAppClassNameField.setAccessible(true);
+ awtAppClassNameField.set(xToolkit, title);
+ }
+ else
{
- quit();
+ jalview.bin.Console.debug("XToolkit: awtAppClassName not found");
}
- });
+ } catch (Exception e)
+ {
+ jalview.bin.Console.debug("Error setting awtAppClassName");
+ jalview.bin.Console.trace(Cache.getStackTraceString(e));
+ }
+ }
+
+ /**
+ * APQHandlers sets handlers for About, Preferences and Quit actions peculiar to
+ * macOS's application menu. APQHandlers will check to see if a handler is
+ * supported before setting it.
+ */
+ try {
+ APQHandlers.setAPQHandlers(this);
+ } catch (Exception e) {
+ System.out.println("Cannot set APQHandlers");
+ // e.printStackTrace();
+ } catch (Throwable t) {
+ jalview.bin.Console.warn("Error setting APQHandlers: " + t.toString());
+ jalview.bin.Console.trace(Cache.getStackTraceString(t));
+ }
+
+ setIconImages(ChannelProperties.getIconList());
+
+ addWindowListener(new WindowAdapter() {
+
+ @Override
+ public void windowClosing(WindowEvent ev) {
+ quit();
+ }
+ });
boolean selmemusage = Cache.getDefault("SHOW_MEMUSAGE", false);
setBounds(xPos, yPos, 900, 650);
}
- getIdentifiersOrgData();
+
if (!Platform.isJS())
/**
* Java only
experimentalFeatures.setSelected(showExperimental());
+ getIdentifiersOrgData();
-
if (Jalview.isInteractive())
{
// disabled for SeqCanvasTest
checkURLLinks();
// Spawn a thread that shows the splashscreen
+
+ if (!nosplash) {
SwingUtilities.invokeLater(new Runnable()
- {
- @Override
- public void run()
- {
- new SplashScreen(true);
- }
- });
+ {
+ @Override
+ public void run()
+ {
+ new SplashScreen(true);
+ }
+ });
+ }
// Thread off a new instance of the file chooser - this reduces the
// time
}).start();
}
- public void getIdentifiersOrgData()
- {
- // Thread off the identifiers fetcher
- new Thread(new Runnable()
- {
- @Override
- public void run()
- {
- Cache.log.debug("Downloading data from identifiers.org");
- try
- {
- UrlDownloadClient.download(IdOrgSettings.getUrl(),
- IdOrgSettings.getDownloadLocation());
- } catch (IOException e)
+ public void getIdentifiersOrgData() {
+ if (Cache.getProperty("NOIDENTIFIERSSERVICE") == null) {
+ // Thread off the identifiers fetcher
+ new Thread(new Runnable() {
+ @Override
+ public void run()
{
- Cache.log.debug("Exception downloading identifiers.org data"
- + e.getMessage());
+ jalview.bin.Console.debug("Downloading data from identifiers.org");
+ try
+ {
+ UrlDownloadClient.download(IdOrgSettings.getUrl(),
+ IdOrgSettings.getDownloadLocation());
+ } catch (IOException e)
+ {
+ jalview.bin.Console.debug("Exception downloading identifiers.org data"
+ + e.getMessage());
+ }
}
- }
- }).start();
+ }).start();
+ }
+
}
@Override
}
}
-// /**
-// * Add an internal frame to the Jalview desktop that is allowed to be resized,
-// * has a minimum size of 300px and might or might not be visible
-// *
-// * @param frame
-// * Frame to show
-// * @param title
-// * Visible Title
-// * @param makeVisible
-// * When true, display frame immediately, otherwise, caller must call
-// * setVisible themselves.
-// * @param w
-// * width
-// * @param h
-// * height
-// */
-// @Deprecated
-// public static synchronized void addInternalFrame(
-// final JInternalFrame frame, String title, boolean makeVisible,
-// int w, int h)
-// {
-// // textbox, web services, sequenceFetcher, featureSettings
-// getInstance().addFrame(frame, title, makeVisible, w, h,
-// FRAME_ALLOW_RESIZE, FRAME_SET_MIN_SIZE_300);
-// }
-//
-// /**
-// * Add an internal frame to the Jalview desktop that is visible, has a minimum
-// * size of 300px, and may or may not be resizable
-// *
-// * @param frame
-// * Frame to show
-// * @param title
-// * Visible Title
-// * @param w
-// * width
-// * @param h
-// * height
-// * @param resizable
-// * Allow resize
-// */
-// @Deprecated
-// public static synchronized void addInternalFrame(
-// final JInternalFrame frame, String title, int w, int h,
-// boolean resizable)
-// {
-// // annotation, font, calculation, user-defined colors
-// getInstance().addFrame(frame, title, FRAME_MAKE_VISIBLE, w, h,
-// resizable, FRAME_SET_MIN_SIZE_300);
-// }
-
/**
* Adds and opens the given frame to the desktop that is visible, allowed to
* resize, and has a 300px minimum width.
int w, int h, boolean resizable, boolean ignoreMinSize)
{
// 15 classes call this method directly.
-
+
-
// TODO: allow callers to determine X and Y position of frame (eg. via
// bounds object).
// TODO: consider fixing method to update entries in the window submenu with
{
openFrameCount++;
--
+
boolean isEmbedded = (Platform.getEmbeddedAttribute(frame, "id") != null);
boolean hasEmbeddedSize = (Platform.getDimIfEmbedded(frame, -1, -1) != null);
// Web page embedding allows us to ignore minimum size
ignoreMinSize |= hasEmbeddedSize;
-
+
if (!ignoreMinSize)
{
-
// Set default dimension for Alignment Frame window.
// The Alignment Frame window could be added from a number of places,
// hence,
message.append(Cache.getDefault("AUTHORFNAMES", DEFAULT_AUTHORS));
message.append(CITATION);
+ message.append("</div>");
-
return message.toString();
}
10, getHeight() - fm.getHeight());
}
}
-
+ // output debug scale message. Important for jalview.bin.HiDPISettingTest2
+ Desktop.debugScaleMessage(Desktop.getDesktopPane().getGraphics());
}
}
startServiceDiscovery(false);
}
+ /**
+ * start service discovery threads - blocking or non-blocking
+ *
+ * @param blocking
+ */
public void startServiceDiscovery(boolean blocking)
{
- System.out.println("Starting service discovery");
+ jalview.bin.Console.debug("Starting service discovery");
-
var tasks = new ArrayList<Future<?>>();
// JAL-940 - JALVIEW 1 services are now being EOLed as of JABA 2.1 release
package jalview.gui;
import jalview.bin.Cache;
+import jalview.bin.Console;
import jalview.util.MessageManager;
import jalview.ws.WSDiscovererI;
- import jalview.ws.slivkaws.SlivkaWSDiscoverer;
+ import jalview.ws2.slivka.SlivkaWSDiscoverer;
+ import jalview.ws2.WebServiceDiscoverer;
import java.awt.BorderLayout;
import java.awt.Color;
sb.append(bitScore);
maxWidth = Math.max(maxWidth, eValue.length());
maxWidth = Math.max(maxWidth, bitScore.length());
+ sb.append("<br>");
}
- sb.append("<br>");
sb.append("</i>");
-
return maxWidth;
}
*/
if (!Platform.isJS())
{
+ tabbedPane.add(initHMMERTab(), MessageManager.getString("label.hmmer"));
+ tabbedPane.add(initStartupTab(),
+ MessageManager.getString("label.startup"));
wsTab.setLayout(new BorderLayout());
tabbedPane.add(wsTab, MessageManager.getString("label.web_services"));
- slivkaTab.setLayout(new BorderLayout());
- tabbedPane.add(slivkaTab, "Slivka Services");
}
+
+ slivkaTab.setLayout(new BorderLayout());
+ tabbedPane.add(slivkaTab, "Slivka Services");
/*
* Handler to validate a tab before leaving it - currently only for
} catch (Throwable throwable)
{
- Cache.log.error("failed to send the job to the alignment server", throwable);
++ Console.error("failed to send the job to the alignment server", throwable);
if (!server.handleSubmitError(throwable, j, wsInfo))
{
if (throwable instanceof Exception)
}
Alignment aln = new Alignment(seqs.toArray(new SequenceI[0]));
if (annotFile == null
- || !new AnnotationFile().readAnnotationFile(aln, annotFile.getURL().toString(), DataSourceType.URL))
+ || !new AnnotationFile().readAnnotationFileWithCalcId(aln, service.getId(), annotFile.getContentUrl().toString(), DataSourceType.URL))
{
- Cache.log.debug("No annotation from slivka job\n" + annotFile);
+ Console.debug("No annotation from slivka job\n" + annotFile);
}
+ else {
- Cache.log.debug("Annotation file loaded " + annotFile);
++ Console.debug("Annotation file loaded " + annotFile);
+ }
if (featFile == null
- || !new FeaturesFile(featFile.getURL().toString(), DataSourceType.URL).parse(aln, featureColours, true))
+ || !new FeaturesFile(featFile.getContentUrl().toString(), DataSourceType.URL).parse(aln, featureColours, true))
{
- Cache.log.debug("No features from slivka job\n" + featFile);
+ Console.debug("No features from slivka job\n" + featFile);
}
+ else {
- Cache.log.debug("Features feil loaded " + featFile);
++ Console.debug("Features feil loaded " + featFile);
+ }
return Arrays.asList(aln.getAlignmentAnnotation());
}
}
package jalview.ws.slivkaws;
-import jalview.bin.Cache;
++
import jalview.datamodel.AlignmentI;
import jalview.datamodel.SequenceI;
import jalview.io.DataSourceType;
import jalview.ws.params.ArgumentI;
import jalview.ws.params.InvalidArgumentException;
import jalview.ws.params.WsParamSetI;
--
import java.io.IOError;
import java.io.IOException;
import java.rmi.ServerError;
{
List<?> services = new SlivkaClient(url).getServices();
return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
- } catch (IOException e)
+ } catch (IOException | org.json.JSONException e)
{
- Cache.log.error("Slivka could not retrieve services list", e);
+ Console.error("Slivka could not retrieve services list", e);
return STATUS_INVALID;
}
}
--- /dev/null
+ package jalview.ws2;
+
+ import java.util.List;
+ import java.util.concurrent.CopyOnWriteArrayList;
+ import java.util.concurrent.Executors;
+ import java.util.concurrent.ScheduledExecutorService;
+ import java.util.concurrent.TimeUnit;
+
+ import jalview.bin.Cache;
++import jalview.bin.Console;
+
+ public class PollingTaskExecutor
+ {
+ private ScheduledExecutorService executor = Executors
+ .newSingleThreadScheduledExecutor();
+
+ public void submit(final PollableTaskI task)
+ {
+ executor.submit(() -> {
+ try
+ {
+ task.start();
+ wsThreadSupport.submitted(task);
+ } catch (Exception e)
+ {
- Cache.log.error("Failed to submit web service jobs.", e);
++ Console.error("Failed to submit web service jobs.", e);
+ wsThreadSupport.submissionFailed(task, e);
+ return;
+ }
+ executor.schedule(() -> poll(task), 1, TimeUnit.SECONDS);
+ });
+ }
+
+ private void poll(PollableTaskI task)
+ {
+ boolean done;
+ try
+ {
+ done = task.poll();
+ } catch (Exception e)
+ {
- Cache.log.error("Failed to poll task.", e);
++ Console.error("Failed to poll task.", e);
+ wsThreadSupport.pollFailed(task, e);
+ return;
+ }
+ if (!done)
+ {
+ executor.schedule(() -> poll(task), 1, TimeUnit.SECONDS);
+ }
+ else
+ {
+ task.done();
+ wsThreadSupport.done(task);
+ }
+ }
+
+ private WebServiceThreadSupport wsThreadSupport = new WebServiceThreadSupport();
+
+ public void addThreadListener(PollableTaskListenerI listener)
+ {
+ wsThreadSupport.addListener(listener);
+ }
+
+ public void removeThreadListener(PollableTaskListenerI listener)
+ {
+ wsThreadSupport.removeListener(listener);
+ }
+
+ public void shutdown()
+ {
+ executor.shutdownNow();
+ }
+
+ }
+
+ class WebServiceThreadSupport implements PollableTaskListenerI
+ {
+ List<PollableTaskListenerI> listeners = new CopyOnWriteArrayList<>();
+
+ @Override
+ public void submitted(PollableTaskI task)
+ {
+ for (var listener : listeners)
+ listener.submitted(task);
+ }
+
+ @Override
+ public void submissionFailed(PollableTaskI task, Exception e)
+ {
+ for (var listener : listeners)
+ listener.submissionFailed(task, e);
+ }
+
+ @Override
+ public void pollFailed(PollableTaskI task, Exception e)
+ {
+ for (var listener : listeners)
+ listener.pollFailed(task, e);
+ }
+
+ @Override
+ public void cancelled(PollableTaskI task)
+ {
+ for (var listener : listeners)
+ listener.cancelled(task);
+ }
+
+ @Override
+ public void done(PollableTaskI task)
+ {
+ for (var listener : listeners)
+ listener.done(task);
+ }
+
+ public void addListener(PollableTaskListenerI listener)
+ {
+ if (!listeners.contains(listener))
+ {
+ listeners.add(listener);
+ }
+ }
+
+ public void removeListener(PollableTaskListenerI listener)
+ {
+ listeners.remove(listener);
+ }
+ }
--- /dev/null
+ package jalview.ws2.operations;
+
+ import static java.lang.String.format;
+
+ import java.awt.event.MouseAdapter;
+ import java.awt.event.MouseEvent;
+ import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.Hashtable;
+ import java.util.LinkedHashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Objects;
+ import java.util.concurrent.CompletionStage;
+
+ import javax.swing.JMenu;
+ import javax.swing.JMenuItem;
+ import javax.swing.ToolTipManager;
+
+ import jalview.analysis.AlignSeq;
+ import jalview.analysis.AlignmentSorter;
+ import jalview.analysis.SeqsetUtils;
+ import jalview.bin.Cache;
++import jalview.bin.Console;
+ import jalview.datamodel.AlignedCodonFrame;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.AlignmentOrder;
+ import jalview.datamodel.AlignmentView;
+ import jalview.datamodel.HiddenColumns;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.Sequence;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.AlignViewport;
+ import jalview.gui.Desktop;
+ import jalview.gui.JvSwingUtils;
+ import jalview.gui.WebserviceInfo;
+ import jalview.gui.WsJobParameters;
+ import jalview.util.MathUtils;
+ import jalview.util.MessageManager;
+ import jalview.ws.params.ArgumentI;
+ import jalview.ws.params.WsParamSetI;
+ import jalview.ws2.MenuEntryProviderI;
+ import jalview.ws2.ResultSupplier;
+ import jalview.ws2.WSJob;
+ import jalview.ws2.WSJobStatus;
+ import jalview.ws2.PollingTaskExecutor;
+ import jalview.ws2.WebServiceI;
+ import jalview.ws2.WebServiceInfoUpdater;
+ import jalview.ws2.WebServiceWorkerI;
+ import jalview.ws2.utils.WSJobList;
+
+ /**
+ *
+ * @author mmwarowny
+ *
+ */
+ public class AlignmentOperation implements Operation
+ {
+ final WebServiceI service;
+
+ final ResultSupplier<AlignmentI> supplier;
+
+ public AlignmentOperation(WebServiceI service,
+ ResultSupplier<AlignmentI> supplier)
+ {
+ this.service = service;
+ this.supplier = supplier;
+ }
+
+ @Override
+ public String getName()
+ {
+ return service.getName();
+ }
+
+ @Override
+ public String getTypeName()
+ {
+ return "Multiple Sequence Alignment";
+ }
+
+ @Override
+ public String getHostName()
+ {
+ return service.getHostName();
+ }
+
+ @Override
+ public int getMinSequences()
+ {
+ return 2;
+ }
+
+ @Override
+ public int getMaxSequences()
+ {
+ return Integer.MAX_VALUE;
+ }
+
+ @Override
+ public boolean isProteinOperation()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isNucleotideOperation()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean isAlignmentAnalysis()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean canSubmitGaps()
+ {
+ // hack copied from original jabaws code, don't blame me
+ return service.getName().contains("lustal");
+ }
+
+ @Override
+ public boolean isInteractive()
+ {
+ return false;
+ }
+
+ @Override
+ public boolean getFilterNonStandardSymbols()
+ {
+ return true;
+ }
+
+ @Override
+ public boolean getNeedsAlignedSequences()
+ {
+ return false;
+ }
+
+ @Override
+ public MenuEntryProviderI getMenuBuilder()
+ {
+ return this::buildMenu;
+ }
+
+ protected void buildMenu(JMenu parent, AlignFrame frame)
+ {
+ if (canSubmitGaps())
+ {
+ var alignSubmenu = new JMenu(service.getName());
+ buildMenu(alignSubmenu, frame, false);
+ parent.add(alignSubmenu);
+ var realignSubmenu = new JMenu(MessageManager.formatMessage(
+ "label.realign_with_params", service.getName()));
+ realignSubmenu.setToolTipText(MessageManager
+ .getString("label.align_sequences_to_existing_alignment"));
+ buildMenu(realignSubmenu, frame, true);
+ parent.add(realignSubmenu);
+ }
+ else
+ {
+ buildMenu(parent, frame, false);
+ }
+ }
+
+ protected void buildMenu(JMenu parent, AlignFrame frame,
+ boolean submitGaps)
+ {
+ final String action = submitGaps ? "Align" : "Realign";
+ final var calcName = service.getName();
+
+ String title = frame.getTitle();
+ PollingTaskExecutor executor = frame.getViewport().getWSExecutor();
+ {
+ var item = new JMenuItem(MessageManager.formatMessage(
+ "label.calcname_with_default_settings", calcName));
+ item.setToolTipText(MessageManager
+ .formatMessage("label.action_with_default_settings", action));
+ item.addActionListener((event) -> {
+ final AlignmentView msa = frame.gatherSequencesForAlignment();
+ final AlignViewport viewport = frame.getViewport();
+ final AlignmentI alignment = frame.getViewport().getAlignment();
+ if (msa != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(msa,
+ Collections.emptyList(), title, submitGaps, true,
+ alignment, viewport);
+ executor.submit(worker);
+ }
+ });
+ parent.add(item);
+ }
+
+ if (service.hasParameters())
+ {
+ var item = new JMenuItem(
+ MessageManager.getString("label.edit_settings_and_run"));
+ item.setToolTipText(MessageManager.getString(
+ "label.view_and_change_parameters_before_alignment"));
+ item.addActionListener((event) -> {
+ final AlignmentView msa = frame.gatherSequencesForAlignment();
+ final AlignViewport viewport = frame.getViewport();
+ final AlignmentI alignment = frame.getViewport().getAlignment();
+ if (msa != null)
+ {
+ openEditParamsDialog(service, null, null)
+ .thenAcceptAsync((arguments) -> {
+ if (arguments != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(msa,
+ arguments, title, submitGaps, true, alignment,
+ viewport);
+ executor.submit(worker);
+ }
+ });
+ }
+ });
+ parent.add(item);
+ }
+
+ var presets = service.getParamStore().getPresets();
+ if (presets != null && presets.size() > 0)
+ {
+ final var presetList = new JMenu(MessageManager
+ .formatMessage("label.run_with_preset_params", calcName));
+ final var showToolTipFor = ToolTipManager.sharedInstance()
+ .getDismissDelay();
+ for (final var preset : presets)
+ {
+ var item = new JMenuItem(preset.getName());
+ final int QUICK_TOOLTIP = 1500;
+ item.addMouseListener(new MouseAdapter()
+ {
+ @Override
+ public void mouseEntered(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(QUICK_TOOLTIP);
+ }
+
+ @Override
+ public void mouseExited(MouseEvent e)
+ {
+ ToolTipManager.sharedInstance().setDismissDelay(showToolTipFor);
+ }
+ });
+ String tooltip = JvSwingUtils.wrapTooltip(true,
+ format("<strong>%s</strong><br/>%s",
+ MessageManager.getString(
+ preset.isModifiable() ? "label.user_preset"
+ : "label.service_preset"),
+ preset.getDescription()));
+ item.setToolTipText(tooltip);
+ item.addActionListener((event) -> {
+ final AlignmentView msa = frame.gatherSequencesForAlignment();
+ final AlignViewport viewport = frame.getViewport();
+ final AlignmentI alignment = frame.getViewport().getAlignment();
+ if (msa != null)
+ {
+ WebServiceWorkerI worker = new AlignmentWorker(msa,
+ preset.getArguments(), title, submitGaps, true,
+ alignment, viewport);
+ executor.submit(worker);
+ }
+ });
+ presetList.add(item);
+ }
+ parent.add(presetList);
+ }
+ }
+
+ private CompletionStage<List<ArgumentI>> openEditParamsDialog(
+ WebServiceI service, WsParamSetI preset,
+ List<ArgumentI> arguments)
+ {
+ WsJobParameters jobParams;
+ if (preset == null && arguments != null && arguments.size() > 0)
+ jobParams = new WsJobParameters(service.getParamStore(), preset,
+ arguments);
+ else
+ jobParams = new WsJobParameters(service.getParamStore(), preset,
+ null);
+ var stage = jobParams.showRunDialog();
+ return stage.thenApply((startJob) -> {
+ if (startJob)
+ {
+ if (jobParams.getPreset() == null)
+ {
+ return jobParams.getJobParams();
+ }
+ else
+ {
+ return jobParams.getPreset().getArguments();
+ }
+ }
+ else
+ {
+ return null;
+ }
+ });
+ }
+
+ /**
+ * Implementation of the web service worker performing multiple sequence
+ * alignment.
+ *
+ * @author mmwarowny
+ *
+ */
+ private class AlignmentWorker implements WebServiceWorkerI
+ {
+
+ private long uid = MathUtils.getUID();
+
+ private final AlignmentView msa;
+
+ private final AlignmentI dataset;
+
+ private final AlignViewport viewport;
+
+ private final List<AlignedCodonFrame> codonFrame = new ArrayList<>();
+
+ private List<ArgumentI> args = Collections.emptyList();
+
+ private String alnTitle = "";
+
+ private boolean submitGaps = false;
+
+ private boolean preserveOrder = false;
+
+ private char gapCharacter;
+
+ private WSJobList jobs = new WSJobList();
+
+ private Map<Long, JobInput> inputs = new LinkedHashMap<>();
+
+ private WebserviceInfo wsInfo;
+
+ private Map<Long, Integer> exceptionCount = new HashMap<>();
+
+ private final int MAX_RETRY = 5;
+
+ AlignmentWorker(AlignmentView msa, List<ArgumentI> args,
+ String alnTitle, boolean submitGaps, boolean preserveOrder,
+ AlignmentI alignment, AlignViewport viewport)
+ {
+ this.msa = msa;
+ this.dataset = alignment.getDataset();
+ List<AlignedCodonFrame> cf = Objects.requireNonNullElse(
+ alignment.getCodonFrames(), Collections.emptyList());
+ this.codonFrame.addAll(cf);
+ this.args = args;
+ this.alnTitle = alnTitle;
+ this.submitGaps = submitGaps;
+ this.preserveOrder = preserveOrder;
+ this.viewport = viewport;
+ this.gapCharacter = viewport.getGapCharacter();
+
+ String panelInfo = String.format("%s using service hosted at %s%n%s",
+ service.getName(), service.getHostName(),
+ Objects.requireNonNullElse(service.getDescription(), ""));
+ wsInfo = new WebserviceInfo(service.getName(), panelInfo, false);
+ }
+
+ @Override
+ public long getUID()
+ {
+ return uid;
+ }
+
+ @Override
+ public WebServiceI getWebService()
+ {
+ return service;
+ }
+
+ @Override
+ public List<WSJob> getJobs()
+ {
+ return Collections.unmodifiableList(jobs);
+ }
+
+ @Override
+ public void start() throws IOException
+ {
- Cache.log.info(format("Starting new %s job.", service.getName()));
++ Console.info(format("Starting new %s job.", service.getName()));
+ String outputHeader = String.format("%s of %s%nJob details%n",
+ submitGaps ? "Re-alignment" : "Alignment", alnTitle);
+ SequenceI[][] conmsa = msa.getVisibleContigs('-');
+ if (conmsa == null)
+ {
+ return;
+ }
+ WebServiceInfoUpdater updater = new WebServiceInfoUpdater(wsInfo);
+ updater.setOutputHeader(outputHeader);
+ int numValid = 0;
+ for (int i = 0; i < conmsa.length; i++)
+ {
+ JobInput input = JobInput.create(conmsa[i], 2, submitGaps);
+ WSJob job = new WSJob(service.getProviderName(), service.getName(),
+ service.getHostName());
+ job.setJobNum(wsInfo.addJobPane());
+ if (conmsa.length > 1)
+ {
+ wsInfo.setProgressName(String.format("region %d", i),
+ job.getJobNum());
+ }
+ wsInfo.setProgressText(job.getJobNum(), outputHeader);
+ job.addPropertyChangeListener(updater);
+ inputs.put(job.getUid(), input);
+ jobs.add(job);
+ if (input.isInputValid())
+ {
+ int count;
+ String jobId = null;
+ do
+ {
+ count = exceptionCount.getOrDefault(job.getUid(), MAX_RETRY);
+ try
+ {
+ jobId = service.submit(input.inputSequences, args);
- Cache.log.debug((format("Job %s submitted", job)));
++ Console.debug((format("Job %s submitted", job)));
+ exceptionCount.remove(job.getUid());
+ } catch (IOException e)
+ {
+ exceptionCount.put(job.getUid(), --count);
+ }
+ } while (jobId == null && count > 0);
+ if (jobId != null)
+ {
+ job.setJobId(jobId);
+ job.setStatus(WSJobStatus.SUBMITTED);
+ numValid++;
+ }
+ else
+ {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ }
+ else
+ {
+ job.setStatus(WSJobStatus.INVALID);
+ job.setErrorLog(
+ MessageManager.getString("label.empty_alignment_job"));
+ }
+ }
+ if (numValid > 0)
+ {
+ // wsInfo.setThisService() should happen here
+ wsInfo.setVisible(true);
+ }
+ else
+ {
+ wsInfo.setVisible(false);
+ // TODO show notification dialog.
+ // JvOptionPane.showMessageDialog(frame,
+ // MessageManager.getString("info.invalid_msa_input_mininfo"),
+ // MessageManager.getString("info.invalid_msa_notenough"),
+ // JvOptionPane.INFORMATION_MESSAGE);
+ }
+ }
+
+ @Override
+ public boolean poll()
+ {
+ boolean done = true;
+ for (WSJob job : getJobs())
+ {
+ if (!job.getStatus().isDone() && !job.getStatus().isFailed())
+ {
- Cache.log.debug(format("Polling job %s.", job));
++ Console.debug(format("Polling job %s.", job));
+ try
+ {
+ service.updateProgress(job);
+ exceptionCount.remove(job.getUid());
+ } catch (IOException e)
+ {
- Cache.log.error(format("Polling job %s failed.", job), e);
++ Console.error(format("Polling job %s failed.", job), e);
+ wsInfo.appendProgressText(job.getJobNum(),
+ MessageManager.formatMessage("info.server_exception",
+ service.getName(), e.getMessage()));
+ int count = exceptionCount.getOrDefault(job.getUid(),
+ MAX_RETRY);
+ if (--count <= 0)
+ {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
- Cache.log.warn(format(
++ Console.warn(format(
+ "Attempts limit exceeded. Droping job %s.", job));
+ }
+ exceptionCount.put(job.getUid(), count);
+ } catch (OutOfMemoryError e)
+ {
+ job.setStatus(WSJobStatus.BROKEN);
- Cache.log.error(
++ Console.error(
+ format("Out of memory when retrieving job %s", job), e);
+ }
- Cache.log.debug(
++ Console.debug(
+ format("Job %s status is %s", job, job.getStatus()));
+ }
+ done &= job.getStatus().isDone() || job.getStatus().isFailed();
+ }
+ updateWSInfoGlobalStatus();
+ return done;
+ }
+
+ private void updateWSInfoGlobalStatus()
+ {
+ if (jobs.countRunning() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_RUNNING);
+ }
+ else if (jobs.countQueuing() > 0
+ || jobs.countSubmitted() < jobs.size())
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_QUEUING);
+ }
+ else
+ {
+ if (jobs.countSuccessful() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_OK);
+ }
+ else if (jobs.countCancelled() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_CANCELLED_OK);
+ }
+ else if (jobs.countFailed() > 0)
+ {
+ wsInfo.setStatus(WebserviceInfo.STATE_STOPPED_ERROR);
+ }
+ }
+ }
+
+ @Override
+ public void done()
+ {
+ long progbarId = MathUtils.getUID();
+ wsInfo.setProgressBar(
+ MessageManager.getString("status.collecting_job_results"),
+ progbarId);
+ Map<Long, AlignmentI> results = new LinkedHashMap<>();
+ for (WSJob job : getJobs())
+ {
+ if (job.getStatus().isFailed())
+ continue;
+ try
+ {
+ AlignmentI alignment = supplier.getResult(job, dataset.getSequences(), viewport);
+ if (alignment != null)
+ {
+ results.put(job.getUid(), alignment);
+ }
+ } catch (Exception e)
+ {
+ if (!service.handleCollectionError(job, e))
+ {
- Cache.log.error("Couldn't get alignment for job.", e);
++ Console.error("Couldn't get alignment for job.", e);
+ // TODO: Increment exception count and retry.
+ job.setStatus(WSJobStatus.SERVER_ERROR);
+ }
+ }
+ }
+ updateWSInfoGlobalStatus();
+ if (results.size() > 0)
+ {
+ OutputWrapper out = prepareOutput(results);
+ wsInfo.showResultsNewFrame.addActionListener(evt -> displayNewFrame(
+ new Alignment(out.aln), out.alorders, out.hidden));
+ wsInfo.setResultsReady();
+ }
+ else
+ {
+ wsInfo.setFinishedNoResults();
+ }
+ wsInfo.removeProgressBar(progbarId);
+ }
+
+ private class OutputWrapper
+ {
+ AlignmentI aln;
+
+ List<AlignmentOrder> alorders;
+
+ HiddenColumns hidden;
+
+ OutputWrapper(AlignmentI aln, List<AlignmentOrder> alorders,
+ HiddenColumns hidden)
+ {
+ this.aln = aln;
+ this.alorders = alorders;
+ this.hidden = hidden;
+ }
+ }
+
+ private OutputWrapper prepareOutput(Map<Long, AlignmentI> alignments)
+ {
+ List<AlignmentOrder> alorders = new ArrayList<>();
+ SequenceI[][] results = new SequenceI[jobs.size()][];
+ AlignmentOrder[] orders = new AlignmentOrder[jobs.size()];
+ for (int i = 0; i < jobs.size(); i++)
+ {
+ WSJob job = jobs.get(i);
+ AlignmentI aln = alignments.get(job.getUid());
+ if (aln != null) // equivalent of job.hasResults()
+ {
+ /* Get the alignment including any empty sequences in the original
+ * order with original ids. */
+ JobInput input = inputs.get(job.getUid());
+ char gapChar = aln.getGapCharacter();
+ List<SequenceI> emptySeqs = input.emptySequences;
+ List<SequenceI> alnSeqs = aln.getSequences();
+ // find the width of the longest sequence
+ int width = 0;
+ for (var seq : alnSeqs)
+ width = Integer.max(width, seq.getLength());
+ for (var emptySeq : emptySeqs)
+ width = Integer.max(width, emptySeq.getLength());
+ // pad shorter sequences with gaps
+ String gapSeq = String.join("",
+ Collections.nCopies(width, Character.toString(gapChar)));
+ List<SequenceI> seqs = new ArrayList<>(
+ alnSeqs.size() + emptySeqs.size());
+ seqs.addAll(alnSeqs);
+ seqs.addAll(emptySeqs);
+ for (var seq : seqs)
+ {
+ if (seq.getLength() < width)
+ seq.setSequence(seq.getSequenceAsString()
+ + gapSeq.substring(seq.getLength()));
+ }
+ SequenceI[] result = seqs.toArray(new SequenceI[0]);
+ AlignmentOrder msaOrder = new AlignmentOrder(result);
+ AlignmentSorter.recoverOrder(result);
+ // temporary workaround for deuniquify
+ @SuppressWarnings({ "rawtypes", "unchecked" })
+ Hashtable names = new Hashtable(input.sequenceNames);
+ // FIXME first call to deuniquify alters original alignment
+ SeqsetUtils.deuniquify(names, result);
+ alorders.add(msaOrder);
+ results[i] = result;
+ orders[i] = msaOrder;
+ }
+ else
+ {
+ results[i] = null;
+ }
+ }
+
+ Object[] newView = msa.getUpdatedView(results, orders, gapCharacter);
+ // free references to original data
+ for (int i = 0; i < jobs.size(); i++)
+ {
+ results[i] = null;
+ orders[i] = null;
+ }
+ SequenceI[] alignment = (SequenceI[]) newView[0];
+ HiddenColumns hidden = (HiddenColumns) newView[1];
+ Alignment aln = new Alignment(alignment);
+ aln.setProperty("Alignment Program", service.getName());
+ if (dataset != null)
+ aln.setDataset(dataset);
+
+ propagateDatasetMappings(aln);
+ return new OutputWrapper(aln, alorders, hidden);
+ // displayNewFrame(aln, alorders, hidden);
+ }
+
+ /*
+ * conserves dataset references to sequence objects returned from web
+ * services. propagate codon frame data to alignment.
+ */
+ private void propagateDatasetMappings(Alignment aln)
+ {
+ if (codonFrame != null)
+ {
+ SequenceI[] alignment = aln.getSequencesArray();
+ for (SequenceI seq : alignment)
+ {
+ for (AlignedCodonFrame acf : codonFrame)
+ {
+ if (acf != null && acf.involvesSequence(seq))
+ {
+ aln.addCodonFrame(acf);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ private void displayNewFrame(AlignmentI aln,
+ List<AlignmentOrder> alorders, HiddenColumns hidden)
+ {
+ AlignFrame frame = new AlignFrame(aln, hidden,
+ AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+ // TODO store feature renderer settings in worker object
+ // frame.getFeatureRenderer().transferSettings(featureSettings);
+ var regions = sortOrders(alorders);
+ if (alorders.size() == 1)
+ {
+ frame.addSortByOrderMenuItem(
+ format("%s Ordering", service.getName()), alorders.get(0));
+ }
+ else
+ {
+ for (int i = 0; i < alorders.size(); i++)
+ {
+ final int j = i;
+ Iterable<String> iter = () -> regions.get(j).stream()
+ .map(it -> Integer.toString(it)).iterator();
+ var orderName = format("%s Region %s Ordering", service.getName(),
+ String.join(",", iter));
+ frame.addSortByOrderMenuItem(orderName, alorders.get(i));
+ }
+ }
+
+ /* TODO
+ * If alignment was requested from one half of a SplitFrame, show in a
+ * SplitFrame with the other pane similarly aligned.
+ */
+
+ Desktop.addInternalFrame(frame, alnTitle, AlignFrame.DEFAULT_WIDTH,
+ AlignFrame.DEFAULT_HEIGHT);
+ }
+
+ private List<List<Integer>> sortOrders(List<?> alorders)
+ {
+ List<List<Integer>> regions = new ArrayList<>();
+ for (int i = 0; i < alorders.size(); i++)
+ {
+ List<Integer> regs = new ArrayList<>();
+ regs.add(i);
+ int j = i + 1;
+ while (j < alorders.size())
+ {
+ if (alorders.get(i).equals(alorders.get(j)))
+ {
+ alorders.remove(j);
+ regs.add(j);
+ }
+ else
+ {
+ j++;
+ }
+ }
+ regions.add(regs);
+ }
+ return regions;
+ }
+ }
+
+ private static class JobInput
+ {
+ final List<SequenceI> inputSequences;
+
+ final List<SequenceI> emptySequences;
+
+ @SuppressWarnings("rawtypes")
+ final Map<String, ? extends Map> sequenceNames;
+
+ private JobInput(int numSequences, List<SequenceI> inputSequences,
+ List<SequenceI> emptySequences,
+ @SuppressWarnings("rawtypes") Map<String, ? extends Map> names)
+ {
+ this.inputSequences = Collections.unmodifiableList(inputSequences);
+ this.emptySequences = Collections.unmodifiableList(emptySequences);
+ this.sequenceNames = names;
+ }
+
+ boolean isInputValid()
+ {
+ return inputSequences.size() >= 2;
+ }
+
+ static JobInput create(SequenceI[] sequences, int minLength,
+ boolean submitGaps)
+ {
+ assert minLength >= 0 : MessageManager.getString(
+ "error.implementation_error_minlen_must_be_greater_zero");
+ int numSeq = 0;
+ for (SequenceI seq : sequences)
+ {
+ if (seq.getEnd() - seq.getStart() >= minLength)
+ {
+ numSeq++;
+ }
+ }
+
+ List<SequenceI> inputSequences = new ArrayList<>();
+ List<SequenceI> emptySequences = new ArrayList<>();
+ @SuppressWarnings("rawtypes")
+ Map<String, Hashtable> names = new LinkedHashMap<>();
+ for (int i = 0; i < sequences.length; i++)
+ {
+ SequenceI seq = sequences[i];
+ String newName = SeqsetUtils.unique_name(i);
+ @SuppressWarnings("rawtypes")
+ Hashtable hash = SeqsetUtils.SeqCharacterHash(seq);
+ names.put(newName, hash);
+ if (numSeq > 1 && seq.getEnd() - seq.getStart() >= minLength)
+ {
+ String seqString = seq.getSequenceAsString();
+ if (!submitGaps)
+ {
+ seqString = AlignSeq.extractGaps(
+ jalview.util.Comparison.GapChars, seqString);
+ }
+ inputSequences.add(new Sequence(newName, seqString));
+ }
+ else
+ {
+ String seqString = "";
+ if (seq.getEnd() >= seq.getStart()) // true if gaps only
+ {
+ seqString = seq.getSequenceAsString();
+ if (!submitGaps)
+ {
+ seqString = AlignSeq.extractGaps(
+ jalview.util.Comparison.GapChars, seqString);
+ }
+ }
+ emptySequences.add(new Sequence(newName, seqString));
+ }
+ }
+
+ return new JobInput(numSeq, inputSequences, emptySequences, names);
+ }
+ }
+
+ }
--- /dev/null
+ package jalview.ws2.operations;
+
+ import java.io.IOException;
+ import java.util.ArrayList;
+ import java.util.Collections;
+ import java.util.HashMap;
+ import java.util.List;
+ import java.util.Map;
+ import java.util.Objects;
+
+ import jalview.analysis.AlignSeq;
+ import jalview.analysis.AlignmentAnnotationUtils;
+ import jalview.analysis.SeqsetUtils;
+ import jalview.api.AlignCalcManagerI2;
+ import jalview.api.AlignViewportI;
+ import jalview.api.AlignmentViewPanel;
+ import jalview.api.FeatureColourI;
+ import jalview.api.PollableAlignCalcWorkerI;
+ import jalview.bin.Cache;
++import jalview.bin.Console;
+ import jalview.datamodel.Alignment;
+ 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.Sequence;
+ import jalview.datamodel.SequenceI;
+ import jalview.datamodel.features.FeatureMatcherSetI;
+ import jalview.gui.AlignFrame;
+ import jalview.gui.AlignViewport;
+ import jalview.gui.IProgressIndicator;
+ import jalview.gui.IProgressIndicatorHandler;
+ import jalview.io.FeaturesFile;
+ import jalview.schemes.FeatureSettingsAdapter;
+ import jalview.schemes.ResidueProperties;
+ import jalview.util.MapList;
+ import jalview.workers.AlignCalcManager2;
+ import jalview.ws.params.ArgumentI;
+ import jalview.ws2.WSJob;
+ import jalview.ws2.WSJobStatus;
+ import jalview.ws2.WebServiceI;
+ import jalview.ws2.gui.ProgressBarUpdater;
+
+ import static java.lang.String.format;
+
+ public class AnnotationServiceWorker implements PollableAlignCalcWorkerI
+ {
+ private AnnotationOperation operation;
+ private WebServiceI service;
+ private List<ArgumentI> args;
+ private AlignViewport viewport;
+ private AlignmentViewPanel alignPanel;
+ List<SequenceI> sequences;
+ private IProgressIndicator progressIndicator;
+ private AlignFrame frame;
+ private final AlignCalcManagerI2 calcMan;
+ private Map<String, SequenceI> seqNames;
+ /**
+ * indicates columns consisting of gaps only
+ */
+ boolean[] gapMap = new boolean[0];
+ int start, end;
+ boolean transferSequenceFeatures = false;
+ private WSJob job;
+ private List<AlignmentAnnotation> ourAnnots;
+
+ private int exceptionCount = MAX_RETRY;
+ private static final int MAX_RETRY = 5;
+
+ AnnotationServiceWorker(AnnotationOperation operation, WebServiceI service,
+ List<ArgumentI> args, AlignViewport viewport, AlignmentViewPanel alignPanel,
+ IProgressIndicator progressIndicator, AlignFrame frame, AlignCalcManagerI2 calcMan)
+ {
+ this.operation = operation;
+ this.service = service;
+ this.args = args;
+ this.viewport = viewport;
+ this.alignPanel = alignPanel;
+ this.progressIndicator = progressIndicator;
+ this.frame = frame;
+ this.calcMan = calcMan;
+ }
+
+ @Override
+ public String getCalcName()
+ {
+ return service.getName();
+ }
+
+ @Override
+ public boolean involves(AlignmentAnnotation annot)
+ {
+ return ourAnnots != null && ourAnnots.contains(annot);
+ }
+
+ @Override
+ public void updateAnnotation()
+ {
+ if (!calcMan.isWorking(this) && job != null && !job.getStatus().isCompleted())
+ {
+ // is it correct to store annotations in a field and use them here?
+ updateResultAnnotation(ourAnnots);
+ }
+ }
+
+ @Override
+ public void removeAnnotation()
+ {
+ if (ourAnnots != null && viewport != null)
+ {
+ AlignmentI alignment = viewport.getAlignment();
+ synchronized (ourAnnots)
+ {
+ for (AlignmentAnnotation aa : ourAnnots)
+ {
+ alignment.deleteAnnotation(aa, true);
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean isDeletable()
+ {
+ return true;
+ }
+
+ @Override
+ public void startUp() throws IOException
+ {
+ if (viewport.isClosed())
+ {
+ return;
+ }
+ /* What "bySequence" means in this context and
+ * what is the SelectionGroup and why is it only relevant when
+ * not dealing with alignment analysis? */
+ var bySequence = !operation.isAlignmentAnalysis();
+ sequences = prepareInput(viewport.getAlignment(),
+ bySequence ? viewport.getSelectionGroup() : null);
+ if (sequences == null)
+ {
- Cache.log.info("Sequences for analysis service were null");
++ Console.info("Sequences for analysis service were null");
+ return;
+ }
+ if (!checkInputSequencesValid(sequences))
+ {
- Cache.log.info("Sequences for analysis service were not valid");
++ Console.info("Sequences for analysis service were not valid");
+ }
- Cache.log.debug(format("submitting %d sequences to %s", sequences.size(),
++ Console.debug(format("submitting %d sequences to %s", sequences.size(),
+ service.getName()));
+ job = new WSJob(service.getProviderName(), service.getName(),
+ service.getHostName());
+ // Should this part be moved out of this class to one of the gui
+ // classes?
+ if (progressIndicator != null)
+ {
+ job.addPropertyChangeListener("status", new ProgressBarUpdater(progressIndicator));
+ progressIndicator.registerHandler(job.getUid(), new IProgressIndicatorHandler()
+ {
+ @Override
+ public boolean cancelActivity(long id)
+ {
+ calcMan.cancelWorker(AnnotationServiceWorker.this);
+ return true;
+ }
+
+ @Override
+ public boolean canCancel()
+ {
+ return isDeletable();
+ }
+ });
+ }
+ String jobId = service.submit(sequences, args);
+ job.setJobId(jobId);
- Cache.log.debug(format("Service %s: submitted job id %s",
++ Console.debug(format("Service %s: submitted job id %s",
+ service.getHostName(), jobId));
+ }
+
+ private List<SequenceI> prepareInput(AlignmentI alignment,
+ AnnotatedCollectionI inputSeqs)
+ {
+ if (alignment == null || alignment.getWidth() <= 0 ||
+ alignment.getSequences() == null)
+ return null;
+ if (alignment.isNucleotide() && !operation.isNucleotideOperation())
+ return null;
+ if (!alignment.isNucleotide() && !operation.isProteinOperation())
+ return null;
+ if (inputSeqs == null || inputSeqs.getWidth() <= 0 ||
+ inputSeqs.getSequences() == null || inputSeqs.getSequences().size() < 1)
+ inputSeqs = alignment;
+
+ List<SequenceI> seqs = new ArrayList<>();
+ final boolean submitGaps = operation.isAlignmentAnalysis();
+ final int minlen = 10;
+ int ln = -1; // I think this variable is redundant
+ if (!operation.isAlignmentAnalysis())
+ seqNames = new HashMap<>();
+ 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())
+ {
+ int sqlen;
+ // is it trying to find the length of a sequence excluding gaps?
+ if (!operation.isAlignmentAnalysis())
+ // why starting at positions to the right from the end/start?
+ sqlen = sq.findPosition(end + 1) - sq.findPosition(start + 1);
+ else
+ sqlen = sq.getEnd() - sq.getStart();
+ if (sqlen >= minlen)
+ {
+ String newName = SeqsetUtils.unique_name(seqs.size());
+ if (seqNames != null)
+ {
+ seqNames.put(newName, sq);
+ }
+ SequenceI seq;
+ if (submitGaps)
+ {
+ seq = new Sequence(newName, sq.getSequenceAsString());
+ seqs.add(seq);
+ 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);
+ boolean isStandard = sq.isProtein() ? ResidueProperties.aaIndex[sqc] < 20
+ : ResidueProperties.nucleotideIndex[sqc] < 5;
+ if (!operation.getFilterNonStandardSymbols() || isStandard)
+ {
+ gapMap[apos] = true;
+ }
+ }
+ }
+ else
+ {
+ // TODO: add ability to exclude hidden regions
+ String sqstring = sq.getSequenceAsString(start, end + 1);
+ seq = new Sequence(newName,
+ AlignSeq.extractGaps(jalview.util.Comparison.GapChars, sqstring));
+ seqs.add(seq);
+ // for annotation need to also record map to sequence start/end
+ // position in range
+ // then transfer back to original sequence on return.
+ }
+ ln = Integer.max(seq.getLength(), ln);
+ }
+ }
+ if (operation.getNeedsAlignedSequences() && submitGaps)
+ {
+ int 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];
+ char[] 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 Sequence(sq.getName(), new String(padded)));
+ }
+ }
+ return seqs;
+ }
+
+ private boolean checkInputSequencesValid(List<SequenceI> sequences)
+ {
+ int nvalid = 0;
+ boolean allowProtein = operation.isProteinOperation(),
+ allowNucleotides = operation.isNucleotideOperation();
+ for (SequenceI sq : sequences)
+ {
+ if (sq.getStart() <= sq.getEnd() &&
+ (sq.isProtein() ? allowProtein : allowNucleotides))
+ {
+ nvalid++;
+ }
+ }
+ return nvalid >= operation.getMinSequences();
+ }
+
+ @Override
+ public boolean poll() throws IOException
+ {
+ if (!job.getStatus().isDone() && !job.getStatus().isFailed())
+ {
- Cache.log.debug(format("Polling job %s", job));
++ Console.debug(format("Polling job %s", job));
+ try
+ {
+ service.updateProgress(job);
+ exceptionCount = MAX_RETRY;
+ } catch (IOException e)
+ {
- Cache.log.error(format("Polling job %s failed.", job), e);
++ Console.error(format("Polling job %s failed.", job), e);
+ if (--exceptionCount <= 0)
+ {
+ job.setStatus(WSJobStatus.SERVER_ERROR);
- Cache.log.warn(format("Attempts limit exceeded. Dropping job %s.", job));
++ Console.warn(format("Attempts limit exceeded. Dropping job %s.", job));
+ }
+ } catch (OutOfMemoryError e)
+ {
+ job.setStatus(WSJobStatus.BROKEN);
- Cache.log.error(format("Out of memory when retrieving job %s", job), e);
++ Console.error(format("Out of memory when retrieving job %s", job), e);
+ }
+ }
+ return job.getStatus().isDone() || job.getStatus().isFailed();
+ }
+
+ @Override
+ public void cancel()
+ {
+ try
+ {
+ service.cancel(job);
+ } catch (IOException e)
+ {
- Cache.log.error(format("Failed to cancel job %s.", job), e);
++ Console.error(format("Failed to cancel job %s.", job), e);
+ }
+ }
+
+ @Override
+ public void done()
+ {
- Cache.log.debug(format("Polling loop exited, job %s is %s", job, job.getStatus()));
++ Console.debug(format("Polling loop exited, job %s is %s", job, job.getStatus()));
+ if (!job.getStatus().isCompleted())
+ {
+ return;
+ }
+ List<AlignmentAnnotation> outputAnnotations = null;
+ try
+ {
+ outputAnnotations = operation.annotationSupplier
+ .getResult(job, sequences, viewport);
+ } catch (IOException e)
+ {
- Cache.log.error(format("Couldn't retrieve features for job %s.", job), e);
++ Console.error(format("Couldn't retrieve features for job %s.", job), e);
+ }
+ if (outputAnnotations != null)
- Cache.log.debug(format("Obtained %d annotation rows.", outputAnnotations.size()));
++ Console.debug(format("Obtained %d annotation rows.", outputAnnotations.size()));
+ else
- Cache.log.debug("Obtained no annotations.");
++ Console.debug("Obtained no annotations.");
+ Map<String, FeatureColourI> featureColours = new HashMap<>();
+ Map<String, FeatureMatcherSetI> featureFilters = new HashMap<>();
+ FeaturesFile featuresFile;
+ try
+ {
+ // I think there should be a better way for obtaining features
+ // Are the features added to the sequences here?
+ featuresFile = operation.featuresSupplier.getResult(job, sequences, viewport);
+ if (featuresFile != null)
+ {
+ Alignment aln = new Alignment(sequences.toArray(new SequenceI[0]));
+ // I do nothing with the featureFilters object
+ featuresFile.parse(aln, featureColours, true);
+ }
+ } catch (IOException e)
+ {
- Cache.log.error(format("Couldn't retrieve features for job %s", job), e);
++ Console.error(format("Couldn't retrieve features for job %s", job), e);
+ }
- Cache.log.debug(format("There are %d feature colours and %d filters.",
++ Console.debug(format("There are %d feature colours and %d filters.",
+ featureColours.size(), featureFilters.size()));
+ if (outputAnnotations != null)
+ {
+ for (AlignmentAnnotation aa : outputAnnotations)
+ {
+ if (aa.getCalcId() == null || aa.getCalcId().equals(""))
+ {
+ aa.setCalcId(service.getName());
+ }
+ // Can't services other than alignment analysis be interactive?
+ // What's the point of storing that information in the annotation?
+ aa.autoCalculated = operation.isAlignmentAnalysis() && operation.isInteractive();
+ }
+ updateResultAnnotation(outputAnnotations);
+ if (transferSequenceFeatures)
+ {
- Cache.log.debug(format("Updating feature display settings and transferring"
++ Console.debug(format("Updating feature display settings and transferring"
+ + "features fron job %s at %s", job, service.getHostName()));
+ viewport.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);
+ }
+ });
+ if (frame.alignPanel == alignPanel)
+ {
+ viewport.setShowSequenceFeatures(true);
+ frame.setMenusForViewport();
+ }
+ }
+ }
- Cache.log.debug("Annotation service task finished.");
++ Console.debug("Annotation service task finished.");
+ }
+
+ // What is the purpose of this method?
+ // When is it called (apart from the above)?
+ private void updateResultAnnotation(List<AlignmentAnnotation> annotations)
+ {
+ var currentAnnotations = Objects.requireNonNullElse(
+ viewport.getAlignment().getAlignmentAnnotation(),
+ new AlignmentAnnotation[0]);
+ List<AlignmentAnnotation> newAnnots = new ArrayList<>();
+ // what is the graph group and why starting from 1?
+ int graphGroup = 1;
+ for (AlignmentAnnotation alna : currentAnnotations)
+ {
+ graphGroup = Integer.max(graphGroup, alna.graphGroup);
+ }
+ for (AlignmentAnnotation ala : annotations)
+ {
+ if (ala.graphGroup > 0)
+ {
+ ala.graphGroup += graphGroup;
+ }
+
+ // stores original sequence, in what case it ends up as null?
+ SequenceI aseq = null;
+ if (ala.sequenceRef != null)
+ {
+ SequenceI seq = seqNames.get(ala.sequenceRef.getName());
+ aseq = seq;
+ while (seq.getDatasetSequence() != null)
+ {
+ seq = seq.getDatasetSequence();
+ }
+ }
+ Annotation[] resAnnot = ala.annotations;
+ Annotation[] gappedAnnot = new Annotation[Math
+ .max(viewport.getAlignment().getWidth(), gapMap.length)];
+ // is it adding gaps which were previously removed to the annotation?
+ 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++];
+ }
+ }
+ // replacing sequence with the original one?
+ ala.sequenceRef = aseq;
+ ala.annotations = gappedAnnot;
+ AlignmentAnnotation newAnnot = viewport.getAlignment()
+ .updateFromOrCopyAnnotation(ala);
+ if (aseq != null)
+ {
+ aseq.addAlignmentAnnotation(newAnnot);
+ newAnnot.adjustForAlignment();
+ AlignmentAnnotationUtils.replaceAnnotationOnAlignmentWith(newAnnot,
+ newAnnot.label, newAnnot.getCalcId());
+ }
+ newAnnots.add(newAnnot);
+ }
+
+ for (SequenceI sq : sequences)
+ {
+ // what are DBRefs? why are they relevant here?
+ if (!sq.getFeatures().hasFeatures() &&
+ (sq.getDBRefs() == null || sq.getDBRefs().size() == 0))
+ {
+ continue;
+ }
+ transferSequenceFeatures = true;
+ SequenceI seq = seqNames.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 sourceStartEnd[] = new int[sourceRange.size() * 2];
+ for (ContiguousI range : sourceRange)
+ {
+ sourceStartEnd[i++] = range.getBegin();
+ sourceStartEnd[i++] = range.getEnd();
+ }
+ Mapping mp = new Mapping(new MapList(sourceStartEnd,
+ new int[] { seq.getStart(), seq.getEnd() }, 1, 1));
+ dseq.transferAnnotation(sq, mp);
+ }
+ updateOurAnnots(newAnnots);
+ }
+
+ protected void updateOurAnnots(List<AlignmentAnnotation> annots)
+ {
+ List<AlignmentAnnotation> our = ourAnnots;
+ ourAnnots = Collections.synchronizedList(annots);
+ AlignmentI alignment = viewport.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 Alignment state
+ synchronized (ourAnnots)
+ {
+ for (AlignmentAnnotation an : ourAnnots)
+ {
+ viewport.getAlignment().validateAnnotation(an);
+ }
+ }
+ // TODO: may need a menu refresh after this
+ // af.setMenusForViewport();
+ alignPanel.adjustAnnotationHeight();
+ }
+ }
--- /dev/null
+ package jalview.ws2.slivka;
+
+ import java.io.IOException;
+ import java.net.MalformedURLException;
+ import java.net.URL;
+ import java.util.*;
+ import java.util.concurrent.*;
+
+ import jalview.bin.Cache;
++import jalview.bin.Console;
+ import jalview.ws2.*;
+ import jalview.ws2.operations.AlignmentOperation;
+ import jalview.ws2.operations.AnnotationOperation;
+ import jalview.ws2.operations.Operation;
+ import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+ import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
+
+ public class SlivkaWSDiscoverer implements WebServiceDiscoverer
+ {
+ private static final String SLIVKA_HOST_URLS = "SLIVKSHOSTURLS";
+
+ private static final String DEFAULT_URL = "https://www.compbio.dundee.ac.uk/slivka/";
+
+ private static SlivkaWSDiscoverer instance = null;
+
+ private List<WebServiceI> services = List.of();
+
+ private SlivkaWSDiscoverer()
+ {
+ }
+
+ public static SlivkaWSDiscoverer getInstance()
+ {
+ if (instance == null)
+ {
+ instance = new SlivkaWSDiscoverer();
+ }
+ return instance;
+ }
+
+ @Override
+ public List<String> getUrls()
+ {
+ String surls = Cache.getDefault(SLIVKA_HOST_URLS, DEFAULT_URL);
+ String urls[] = surls.split(",");
+ ArrayList<String> valid = new ArrayList<>(urls.length);
+ for (String url : urls)
+ {
+ try
+ {
+ new URL(url);
+ valid.add(url);
+ } catch (MalformedURLException e)
+ {
- Cache.log.warn("Problem whilst trying to make a URL from '"
++ Console.warn("Problem whilst trying to make a URL from '"
+ + Objects.toString(url, "<null>") + "'. "
+ + "This was probably due to malformed comma-separated-list "
+ + "in the " + SLIVKA_HOST_URLS
+ + " entry of ${HOME}/.jalview_properties");
- Cache.log.debug("Exception occurred while reading url list", e);
++ Console.debug("Exception occurred while reading url list", e);
+ }
+ }
+ return valid;
+ }
+
+ @Override
+ public void setUrls(List<String> wsUrls)
+ {
+ if (wsUrls != null && !wsUrls.isEmpty())
+ {
+ Cache.setProperty(SLIVKA_HOST_URLS, String.join(",", wsUrls));
+ }
+ else
+ {
+ Cache.removeProperty(SLIVKA_HOST_URLS);
+ }
+ }
+
+ @Override
+ public boolean testUrl(URL url)
+ {
+ return getStatusForUrl(url.toString()) == STATUS_OK;
+ }
+
+ @Override
+ public int getStatusForUrl(String url)
+ {
+ try
+ {
+ List<?> services = new SlivkaClient(url).getServices();
+ return services.isEmpty() ? STATUS_NO_SERVICES : STATUS_OK;
+ } catch (IOException e)
+ {
- Cache.log.error("Slivka could not retrieve services list from " + url,
++ Console.error("Slivka could not retrieve services list from " + url,
+ e);
+ return STATUS_INVALID;
+ }
+ }
+
+ public List<WebServiceI> getServices()
+ {
+ return Collections.unmodifiableList(services);
+ }
+
+ public boolean hasServices()
+ {
+ return !isRunning() && services.size() > 0;
+ }
+
+ public boolean isRunning()
+ {
+ for (Future<?> task : discoveryTasks)
+ {
+ if (!task.isDone())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ public boolean isDone()
+ {
+ return !isRunning() && discoveryTasks.size() > 0;
+ }
+
+ private Vector<Future<?>> discoveryTasks = new Vector<>();
+
+ @Override
+ public CompletableFuture<WebServiceDiscoverer> startDiscoverer()
+ {
+ CompletableFuture<WebServiceDiscoverer> task = CompletableFuture
+ .supplyAsync(() -> {
+ reloadServices();
+ return SlivkaWSDiscoverer.this;
+ });
+ task.thenRun(() -> fireServicesChanged(getServices()));
+ discoveryTasks.add(task);
+ return task;
+ }
+
+ private List<WebServiceI> reloadServices()
+ {
- Cache.log.info("Reloading Slivka services");
++ Console.info("Reloading Slivka services");
+ fireServicesChanged(Collections.emptyList());
+ ArrayList<WebServiceI> allServices = new ArrayList<>();
+ for (String url : getUrls())
+ {
+ SlivkaClient client = new SlivkaClient(url);
+ List<SlivkaService> services;
+ try
+ {
+ services = client.getServices();
+ } catch (IOException e)
+ {
- Cache.log.error("Unable to fetch services from " + url, e);
++ Console.error("Unable to fetch services from " + url, e);
+ continue;
+ }
+ for (SlivkaService service : services)
+ {
+ SlivkaWebService instance = new SlivkaWebService(client, service);
+ for (String classifier : service.classifiers)
+ {
+ String[] path = classifier.split("\\s*::\\s*");
+ if (path.length >= 3 && path[0].toLowerCase().equals("operation")
+ && path[1].toLowerCase().equals("analysis"))
+ {
+ Operation op = null;
+ switch (path[path.length - 1].toLowerCase())
+ {
+ case "sequence alignment analysis (conservation)":
+ AnnotationOperation anop;
+ op = anop = new AnnotationOperation(instance,
+ instance::getAnnotations, instance::getFeaturesFile, "Conservation");
+ anop.setAlignmentAnalysis(true);
+ anop.setInteractive(true);
+ break;
+ case "protein sequence analysis":
+ op = new AnnotationOperation(instance, instance::getAnnotations,
+ instance::getFeaturesFile, "Protein Disorder");
+ break;
+ case "multiple sequence alignment":
+ op = new AlignmentOperation(instance, instance::getAlignment);
+ break;
+ }
+ if (op != null)
+ {
+ instance.addOperation(op);
+ break;
+ }
+ }
+ }
+ if (instance.operations.size() > 0)
+ {
+ allServices.add(instance);
+ }
+ }
+ }
+ this.services = allServices;
- Cache.log.info("Reloading slivka services finished");
++ Console.info("Reloading slivka services finished");
+ return allServices;
+ }
+
+ @Override
+ public String getErrorMessages()
+ {
+ return "";
+ }
+
+ }
--- /dev/null
+ package jalview.ws2.slivka;
+
+ import java.io.ByteArrayInputStream;
+ import java.io.ByteArrayOutputStream;
+ import java.io.IOException;
+ import java.io.InputStream;
+ import java.util.ArrayList;
+ import java.util.Arrays;
+ import java.util.Collection;
+ import java.util.EnumMap;
+ import java.util.HashSet;
+ import java.util.List;
+ import java.util.Set;
+
+ import jalview.api.AlignViewportI;
+ import jalview.bin.Cache;
++import jalview.bin.Console;
+ import jalview.datamodel.Alignment;
+ import jalview.datamodel.AlignmentAnnotation;
+ import jalview.datamodel.AlignmentI;
+ import jalview.datamodel.SequenceI;
+ import jalview.io.AnnotationFile;
+ import jalview.io.DataSourceType;
+ import jalview.io.FeaturesFile;
+ import jalview.io.FileFormat;
+ import jalview.io.FileFormatI;
+ import jalview.io.FormatAdapter;
+ import jalview.ws.gui.WsJob;
+ import jalview.ws.params.ArgumentI;
+ import jalview.ws.params.ParamDatastoreI;
+ import jalview.ws.params.WsParamSetI;
+ import jalview.ws.slivkaws.SlivkaDatastore;
+ import jalview.ws2.WebServiceI;
+ import jalview.ws2.operations.Operation;
+ import jalview.ws2.ResultSupplier;
+ import jalview.ws2.WSJob;
+ import jalview.ws2.WSJobStatus;
+ import javajs.http.ClientProtocolException;
+ import uk.ac.dundee.compbio.slivkaclient.Job;
+ import uk.ac.dundee.compbio.slivkaclient.Parameter;
+ import uk.ac.dundee.compbio.slivkaclient.RemoteFile;
+ import uk.ac.dundee.compbio.slivkaclient.SlivkaClient;
+ import uk.ac.dundee.compbio.slivkaclient.SlivkaService;
+
+ public class SlivkaWebService implements WebServiceI
+ {
+ protected final SlivkaClient client;
+
+ protected final SlivkaService service;
+
+ protected SlivkaDatastore store = null;
+
+ protected final ArrayList<Operation> operations = new ArrayList<>();
+
+ protected int typeFlags = 0;
+
+ protected static final EnumMap<Job.Status, WSJobStatus> statusMap = new EnumMap<>(
+ Job.Status.class);
+ {
+ statusMap.put(Job.Status.PENDING, WSJobStatus.SUBMITTED);
+ statusMap.put(Job.Status.REJECTED, WSJobStatus.INVALID);
+ statusMap.put(Job.Status.ACCEPTED, WSJobStatus.QUEUED);
+ statusMap.put(Job.Status.QUEUED, WSJobStatus.QUEUED);
+ statusMap.put(Job.Status.RUNNING, WSJobStatus.RUNNING);
+ statusMap.put(Job.Status.COMPLETED, WSJobStatus.FINISHED);
+ statusMap.put(Job.Status.INTERRUPTED, WSJobStatus.CANCELLED);
+ statusMap.put(Job.Status.DELETED, WSJobStatus.CANCELLED);
+ statusMap.put(Job.Status.FAILED, WSJobStatus.FAILED);
+ statusMap.put(Job.Status.ERROR, WSJobStatus.SERVER_ERROR);
+ statusMap.put(Job.Status.UNKNOWN, WSJobStatus.UNKNOWN);
+ }
+
+ public SlivkaWebService(SlivkaClient client, SlivkaService service)
+ {
+ this.client = client;
+ this.service = service;
+ }
+
+ @Override
+ public String getHostName()
+ {
+ return client.getUrl().toString();
+ }
+
+ @Override
+ public String getProviderName()
+ {
+ return "slivka";
+ }
+
+ @Override
+ public String getName()
+ {
+ return service.getName();
+ }
+
+ @Override
+ public String getDescription()
+ {
+ return service.getDescription();
+ }
+
+ @Override
+ public List<Operation> getOperations()
+ {
+ return operations;
+ }
+
+ void addOperation(Operation operation)
+ {
+ operations.add(operation);
+ }
+
+ void removeOperation(Operation operation)
+ {
+ operations.remove(operation);
+ }
+
+ @Override
+ public boolean hasParameters()
+ {
+ return getParamStore().getServiceParameters().size() > 0;
+ }
+
+ @Override
+ public ParamDatastoreI getParamStore()
+ {
+ if (store == null)
+ {
+ store = new SlivkaDatastore(service);
+ }
+ return store;
+ }
+
+ @Override
+ public String submit(List<SequenceI> sequences, List<ArgumentI> args)
+ throws IOException
+ {
+ var request = new uk.ac.dundee.compbio.slivkaclient.JobRequest();
+ for (Parameter param : service.getParameters())
+ {
+ if (param instanceof Parameter.FileParameter)
+ {
+ // if finds a file input, gives it sequences stream
+ Parameter.FileParameter fileParam = (Parameter.FileParameter) param;
+ FileFormat format;
+ switch (fileParam.getMediaType())
+ {
+ case "application/pfam":
+ format = FileFormat.Pfam;
+ break;
+ case "application/stockholm":
+ format = FileFormat.Stockholm;
+ break;
+ case "application/clustal":
+ format = FileFormat.Clustal;
+ break;
+ case "application/fasta":
+ default:
+ format = FileFormat.Fasta;
+ break;
+ }
+ InputStream stream = new ByteArrayInputStream(format.getWriter(null)
+ .print(sequences.toArray(new SequenceI[0]), false)
+ .getBytes());
+ request.addFile(param.getId(), stream);
+ }
+ }
+ if (args != null)
+ {
+ for (ArgumentI arg : args)
+ {
+ // multiple choice field names are name$number to avoid duplications
+ // the number is stripped here
+ String paramId = arg.getName().split("\\$", 2)[0];
+ Parameter param = service.getParameter(paramId);
+ if (param instanceof Parameter.FlagParameter)
+ {
+ if (arg.getValue() != null && !arg.getValue().isBlank())
+ request.addData(paramId, true);
+ else
+ request.addData(paramId, false);
+ }
+ else
+ {
+ request.addData(paramId, arg.getValue());
+ }
+ }
+ }
+ var job = service.submitJob(request);
+ return job.getId();
+ }
+
+ @Override
+ public void updateProgress(WSJob job) throws IOException
+ {
+ var slivkaJob = client.getJob(job.getJobId());
+ job.setStatus(statusMap.get(slivkaJob.getStatus()));
+ Collection<RemoteFile> files = slivkaJob.getResults();
+ for (RemoteFile f : files)
+ {
+ if (f.getLabel().equals("log"))
+ {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ f.writeTo(stream);
+ job.setLog(stream.toString("UTF-8"));
+ }
+ else if (f.getLabel().equals("error-log"))
+ {
+ ByteArrayOutputStream stream = new ByteArrayOutputStream();
+ f.writeTo(stream);
+ job.setErrorLog(stream.toString("UTF-8"));
+ }
+ }
+ }
+
+ @Override
+ public void cancel(WSJob job) throws IOException
+ {
+ job.setStatus(WSJobStatus.CANCELLED);
- Cache.log.warn("Slivka does not support job cancellation yet.");
++ Console.warn("Slivka does not support job cancellation yet.");
+ }
+
+ @Override
+ public boolean handleSubmissionError(WSJob job, Exception ex)
+ {
+ if (ex instanceof ClientProtocolException)
+ {
- Cache.log.error("Job submission failed due to exception.", ex);
++ Console.error("Job submission failed due to exception.", ex);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean handleCollectionError(WSJob job, Exception ex)
+ {
+ // TODO Auto-generated method stub
+ return false;
+ }
+
+ public AlignmentI getAlignment(WSJob job, List<SequenceI> dataset,
+ AlignViewportI viewport) throws IOException
+ {
+ Collection<RemoteFile> files;
+ var slivkaJob = client.getJob(job.getJobId());
+ files = slivkaJob.getResults();
+ for (RemoteFile f : files)
+ {
+ if (f.getMediaType().equals("application/clustal"))
+ {
+ return new FormatAdapter().readFile(f.getContentUrl().toString(),
+ DataSourceType.URL, FileFormat.Clustal);
+ }
+ else if (f.getMediaType().equals("application/fasta"))
+ {
+ return new FormatAdapter().readFile(f.getContentUrl().toString(),
+ DataSourceType.URL, FileFormat.Fasta);
+ }
+ }
+ return null;
+ }
+
+ public FeaturesFile getFeaturesFile(WSJob job,
+ List<SequenceI> dataset, AlignViewportI viewport) throws IOException
+ {
+ var slivkaJob = client.getJob(job.getJobId());
+ Collection<RemoteFile> files = slivkaJob.getResults();
+ for (RemoteFile f : files)
+ {
+ if (f.getMediaType().equals("application/jalview-features"))
+ {
+ return new FeaturesFile(f.getContentUrl().toString(), DataSourceType.URL);
+ }
+ }
+ return null;
+ }
+
+ public List<AlignmentAnnotation> getAnnotations(WSJob job,
+ List<SequenceI> dataset, AlignViewportI viewport) throws IOException
+ {
+ var slivkaJob = client.getJob(job.getJobId());
+ Collection<RemoteFile> files = slivkaJob.getResults();
+ for (RemoteFile f : files)
+ {
+ if (f.getMediaType().equals("application/jalview-annotations"))
+ {
+ Alignment aln = new Alignment(dataset.toArray(new SequenceI[0]));
+ AnnotationFile af = new AnnotationFile();
+ boolean valid = af.readAnnotationFileWithCalcId(aln, service.getId(),
+ f.getContentUrl().toString(), DataSourceType.URL);
+ if (valid)
+ {
+ return Arrays.asList(aln.getAlignmentAnnotation());
+ }
+ else
+ {
+ throw new IOException("Unable to read annotations from file " +
+ f.getContentUrl().toString());
+ }
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.format("SlivkaWebService[%s]", getName());
+ }
+ }