X-Git-Url: http://source.jalview.org/gitweb/?a=blobdiff_plain;f=src%2Fjalview%2Fext%2Frbvi%2Fchimera%2FJalviewChimeraBinding.java;h=00446f2ab334d297adb824bfed7a4370a55dee21;hb=3d0101179759ef157b088ea135423cd909512d9f;hp=18a614dd60a7d33725d2a5404255fc99ae27531d;hpb=acdc158f5c20ab04bb40e3b0580f3da02e4f744e;p=jalview.git diff --git a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java index 18a614d..00446f2 100644 --- a/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java +++ b/src/jalview/ext/rbvi/chimera/JalviewChimeraBinding.java @@ -21,17 +21,18 @@ package jalview.ext.rbvi.chimera; import jalview.api.AlignmentViewPanel; -import jalview.api.FeatureRenderer; import jalview.api.SequenceRenderer; +import jalview.api.structures.JalviewStructureDisplayI; import jalview.bin.Cache; import jalview.datamodel.AlignmentI; -import jalview.datamodel.ColumnSelection; +import jalview.datamodel.HiddenColumns; import jalview.datamodel.PDBEntry; -import jalview.datamodel.SearchResults; -import jalview.datamodel.SearchResults.Match; +import jalview.datamodel.SearchResultMatchI; +import jalview.datamodel.SearchResultsI; import jalview.datamodel.SequenceFeature; import jalview.datamodel.SequenceI; import jalview.httpserver.AbstractRequestHandler; +import jalview.io.DataSourceType; import jalview.schemes.ColourSchemeI; import jalview.schemes.ResidueProperties; import jalview.structure.AtomSpec; @@ -47,7 +48,9 @@ import java.io.IOException; import java.io.PrintWriter; import java.net.BindException; import java.util.ArrayList; +import java.util.BitSet; import java.util.Collections; +import java.util.Hashtable; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -61,8 +64,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel { public static final String CHIMERA_FEATURE_GROUP = "Chimera"; - private static final String CHIMERA_FEATURE_PREFIX = "chim_"; - // Chimera clause to exclude alternate locations in atom selection private static final String NO_ALTLOCS = "&~@.B-Z&~@.2-9"; @@ -75,6 +76,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel private static final String ALPHACARBON = "CA"; + private List chainNames = new ArrayList(); + + private Hashtable chainFile = new Hashtable(); + /* * Object through which we talk to Chimera */ @@ -98,8 +103,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private boolean loadingFinished = true; - public String fileLoadingError; - /* * Map of ChimeraModel objects keyed by PDB full local file name */ @@ -114,6 +117,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ private long loadNotifiesHandled = 0; + private Thread chimeraMonitor; + /** * Open a PDB structure file in Chimera and set up mappings from Jalview. * @@ -167,13 +172,6 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel if (getSsm() != null) { getSsm().addStructureViewerListener(this); - // ssm.addSelectionListener(this); - FeatureRenderer fr = getFeatureRenderer(null); - if (fr != null) - { - fr.featuresAdded(); - } - refreshGUI(); } return true; } catch (Exception q) @@ -191,16 +189,45 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * @param ssm * @param pdbentry * @param sequenceIs - * @param chains * @param protocol */ public JalviewChimeraBinding(StructureSelectionManager ssm, - PDBEntry[] pdbentry, SequenceI[][] sequenceIs, String[][] chains, - String protocol) + PDBEntry[] pdbentry, SequenceI[][] sequenceIs, + DataSourceType protocol) + { + super(ssm, pdbentry, sequenceIs, protocol); + viewer = new ChimeraManager(new StructureManager(true)); + } + + /** + * Starts a thread that waits for the Chimera process to finish, so that we + * can then close the associated resources. This avoids leaving orphaned + * Chimera viewer panels in Jalview if the user closes Chimera. + */ + protected void startChimeraProcessMonitor() { - super(ssm, pdbentry, sequenceIs, chains, protocol); - viewer = new ChimeraManager( - new ext.edu.ucsf.rbvi.strucviz2.StructureManager(true)); + final Process p = viewer.getChimeraProcess(); + chimeraMonitor = new Thread(new Runnable() + { + + @Override + public void run() + { + try + { + p.waitFor(); + JalviewStructureDisplayI display = getViewer(); + if (display != null) + { + display.closeViewer(false); + } + } catch (InterruptedException e) + { + // exit thread if Chimera Viewer is closed in Jalview + } + } + }); + chimeraMonitor.start(); } /** @@ -215,24 +242,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel viewer.startListening(chimeraListener.getUri()); } catch (BindException e) { - System.err.println("Failed to start Chimera listener: " - + e.getMessage()); + System.err.println( + "Failed to start Chimera listener: " + e.getMessage()); } } /** - * Construct a title string for the viewer window based on the data Jalview - * knows about - * - * @param verbose - * @return - */ - public String getViewerTitle(boolean verbose) - { - return getViewerTitle(CHIMERA_FEATURE_GROUP, verbose); - } - - /** * Tells Chimera to display only the specified chains * * @param toshow @@ -248,11 +263,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel boolean first = true; for (String chain : toshow) { + int modelNumber = getModelNoForChain(chain); + String showChainCmd = modelNumber == -1 ? "" + : modelNumber + ":." + chain.split(":")[1]; if (!first) { cmd.append(","); } - cmd.append(":.").append(chain); + cmd.append(showChainCmd); first = false; } @@ -261,7 +279,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * window, but it looks more helpful not to (easier to relate chains to the * whole) */ - final String command = "~display #*; ~ribbon #*; ribbon " + final String command = "~display #*; ~ribbon #*; ribbon :" + cmd.toString(); sendChimeraCommand(command, false); } @@ -272,7 +290,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel */ public void closeViewer(boolean closeChimera) { - getSsm().removeStructureViewerListener(this, this.getPdbFile()); + getSsm().removeStructureViewerListener(this, this.getStructureFiles()); if (closeChimera) { viewer.exitChimera(); @@ -284,9 +302,14 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } viewer = null; + if (chimeraMonitor != null) + { + chimeraMonitor.interrupt(); + } releaseUIResources(); } + @Override public void colourByChain() { colourBySequence = false; @@ -302,6 +325,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel *
  • all others - white
  • * */ + @Override public void colourByCharge() { colourBySequence = false; @@ -310,28 +334,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /** - * Construct and send a command to align structures against a reference - * structure, based on one or more sequence alignments - * - * @param _alignment - * an array of alignments to process - * @param _refStructure - * an array of corresponding reference structures (index into pdb - * file array); if a negative value is passed, the first PDB file - * mapped to an alignment sequence is used as the reference for - * superposition - * @param _hiddenCols - * an array of corresponding hidden columns for each alignment + * {@inheritDoc} */ - public void superposeStructures(AlignmentI[] _alignment, - int[] _refStructure, ColumnSelection[] _hiddenCols) + @Override + public String superposeStructures(AlignmentI[] _alignment, + int[] _refStructure, HiddenColumns[] _hiddenCols) { StringBuilder allComs = new StringBuilder(128); - String[] files = getPdbFile(); + String[] files = getStructureFiles(); if (!waitForFileLoad(files)) { - return; + return null; } refreshPdbEntries(); @@ -340,7 +354,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel { int refStructure = _refStructure[a]; AlignmentI alignment = _alignment[a]; - ColumnSelection hiddenCols = _hiddenCols[a]; + HiddenColumns hiddenCols = _hiddenCols[a]; if (refStructure >= files.length) { @@ -350,13 +364,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } /* - * 'matched' array will hold 'true' for visible alignment columns where + * 'matched' bit i will be set for visible alignment columns i where * all sequences have a residue with a mapping to the PDB structure */ - boolean matched[] = new boolean[alignment.getWidth()]; - for (int m = 0; m < matched.length; m++) + BitSet matched = new BitSet(); + for (int m = 0; m < alignment.getWidth(); m++) { - matched[m] = (hiddenCols != null) ? hiddenCols.isVisible(m) : true; + if (hiddenCols == null || hiddenCols.isVisible(m)) + { + matched.set(m); + } } SuperposeData[] structures = new SuperposeData[files.length]; @@ -380,17 +397,11 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel refStructure = candidateRefStructure; } - int nmatched = 0; - for (boolean b : matched) - { - if (b) - { - nmatched++; - } - } + int nmatched = matched.cardinality(); if (nmatched < 4) { - // TODO: bail out here because superposition illdefined? + return MessageManager.formatMessage("label.insufficient_residues", + nmatched); } /* @@ -403,41 +414,41 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel int lpos = -1; boolean run = false; StringBuilder molsel = new StringBuilder(); - for (int r = 0; r < matched.length; r++) + + int nextColumnMatch = matched.nextSetBit(0); + while (nextColumnMatch != -1) { - if (matched[r]) + int pdbResNum = structures[pdbfnum].pdbResNo[nextColumnMatch]; + if (lpos != pdbResNum - 1) { - int pdbResNum = structures[pdbfnum].pdbResNo[r]; - if (lpos != pdbResNum - 1) + /* + * discontiguous - append last residue now + */ + if (lpos != -1) { - /* - * discontiguous - append last residue now - */ - if (lpos != -1) - { - molsel.append(String.valueOf(lpos)); - molsel.append(chainCd); - molsel.append(","); - } - run = false; + molsel.append(String.valueOf(lpos)); + molsel.append(chainCd); + molsel.append(","); } - else + run = false; + } + else + { + /* + * extending a contiguous run + */ + if (!run) { /* - * extending a contiguous run + * start the range selection */ - if (!run) - { - /* - * start the range selection - */ - molsel.append(String.valueOf(lpos)); - molsel.append("-"); - } - run = true; + molsel.append(String.valueOf(lpos)); + molsel.append("-"); } - lpos = pdbResNum; + run = true; } + lpos = pdbResNum; + nextColumnMatch = matched.nextSetBit(nextColumnMatch + 1); } /* @@ -505,14 +516,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel if (debug) { System.out.println("Select regions:\n" + selectioncom.toString()); - System.out.println("Superimpose command(s):\n" - + command.toString()); + System.out.println( + "Superimpose command(s):\n" + command.toString()); } allComs.append("~display all; chain @CA|P; ribbon ") .append(selectioncom.toString()) .append(";" + command.toString()); } } + + String error = null; if (selectioncom.length() > 0) { // TODO: visually distinguish regions that were superposed @@ -526,9 +539,17 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel } allComs.append("; ~display all; chain @CA|P; ribbon ") .append(selectioncom.toString()).append("; focus"); - sendChimeraCommand(allComs.toString(), false); + List chimeraReplies = sendChimeraCommand(allComs.toString(), + true); + for (String reply : chimeraReplies) + { + if (reply.toLowerCase().contains("unequal numbers of atoms")) + { + error = reply; + } + } } - + return error; } /** @@ -557,30 +578,36 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * to the Chimera command 'list models type molecule', see * ChimeraManager.getModelList(). */ - List maps = chimeraMaps.get(getPdbFile()[pdbfnum]); + List maps = chimeraMaps.get(getStructureFiles()[pdbfnum]); boolean hasSubModels = maps != null && maps.size() > 1; return "#" + String.valueOf(pdbfnum) + (hasSubModels ? ".1" : ""); } /** * Launch Chimera, unless an instance linked to this object is already - * running. Returns true if chimera is successfully launched, or already + * running. Returns true if Chimera is successfully launched, or already * running, else false. * * @return */ public boolean launchChimera() { - if (!viewer.isChimeraLaunched()) - { - return viewer.launchChimera(StructureManager.getChimeraPaths()); - } if (viewer.isChimeraLaunched()) { return true; } - log("Failed to launch Chimera!"); - return false; + + boolean launched = viewer + .launchChimera(StructureManager.getChimeraPaths()); + if (launched) + { + startChimeraProcessMonitor(); + } + else + { + log("Failed to launch Chimera!"); + } + return launched; } /** @@ -644,39 +671,35 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel String progressMsg); /** - * colour any structures associated with sequences in the given alignment - * using the getFeatureRenderer() and getSequenceRenderer() renderers but only - * if colourBySequence is enabled. + * Sends a set of colour commands to the structure viewer + * + * @param colourBySequenceCommands */ - public void colourBySequence(boolean showFeatures, - jalview.api.AlignmentViewPanel alignmentv) + @Override + protected void colourBySequence( + StructureMappingcommandSet[] colourBySequenceCommands) { - if (!colourBySequence || !loadingFinished) - { - return; - } - if (getSsm() == null) + for (StructureMappingcommandSet cpdbbyseq : colourBySequenceCommands) { - return; - } - String[] files = getPdbFile(); - - SequenceRenderer sr = getSequenceRenderer(alignmentv); - - FeatureRenderer fr = null; - if (showFeatures) - { - fr = getFeatureRenderer(alignmentv); + for (String command : cpdbbyseq.commands) + { + sendAsynchronousCommand(command, COLOURING_CHIMERA); + } } - AlignmentI alignment = alignmentv.getAlignment(); + } - StructureMappingcommandSet colourBySequenceCommands = ChimeraCommands - .getColourBySequenceCommand(getSsm(), files, getSequence(), sr, - fr, alignment); - for (String command : colourBySequenceCommands.commands) - { - sendAsynchronousCommand(command, COLOURING_CHIMERA); - } + /** + * @param files + * @param sr + * @param viewPanel + * @return + */ + @Override + protected StructureMappingcommandSet[] getColourBySequenceCommands( + String[] files, SequenceRenderer sr, AlignmentViewPanel viewPanel) + { + return ChimeraCommands.getColourBySequenceCommand(getSsm(), files, + getSequence(), sr, viewPanel); } /** @@ -706,40 +729,12 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel // ////////////////////////// /** - * returns the current featureRenderer that should be used to colour the - * structures - * - * @param alignment - * - * @return - */ - public abstract FeatureRenderer getFeatureRenderer( - AlignmentViewPanel alignment); - - /** * instruct the Jalview binding to update the pdbentries vector if necessary * prior to matching the viewer's contents to the list of structure files * Jalview knows about. */ public abstract void refreshPdbEntries(); - private int getModelNum(String modelFileName) - { - String[] mfn = getPdbFile(); - if (mfn == null) - { - return -1; - } - for (int i = 0; i < mfn.length; i++) - { - if (mfn[i].equalsIgnoreCase(modelFileName)) - { - return i; - } - } - return -1; - } - /** * map between index of model filename returned from getPdbFile and the first * index of models from this file in the viewer. Note - this is not trimmed - @@ -750,39 +745,16 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel // //////////////////////////////// // /StructureListener @Override - public synchronized String[] getPdbFile() + public synchronized String[] getStructureFiles() { if (viewer == null) { return new String[0]; } - // if (modelFileNames == null) - // { - // Collection chimodels = viewer.getChimeraModels(); - // _modelFileNameMap = new int[chimodels.size()]; - // int j = 0; - // for (ChimeraModel chimodel : chimodels) - // { - // String mdlName = chimodel.getModelName(); - // } - // modelFileNames = new String[j]; - // // System.arraycopy(mset, 0, modelFileNames, 0, j); - // } - - return chimeraMaps.keySet().toArray( - modelFileNames = new String[chimeraMaps.size()]); - } - /** - * returns the current sequenceRenderer that should be used to colour the - * structures - * - * @param alignment - * - * @return - */ - public abstract SequenceRenderer getSequenceRenderer( - AlignmentViewPanel alignment); + return chimeraMaps.keySet() + .toArray(modelFileNames = new String[chimeraMaps.size()]); + } /** * Construct and send a command to highlight zero, one or more atoms. We do @@ -865,7 +837,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * Parse model number, residue and chain for each selected position, * formatted as #0:123.A or #1.2:87.B (#model.submodel:residue.chain) */ - List atomSpecs = convertStructureResiduesToAlignment(selection); + List atomSpecs = convertStructureResiduesToAlignment( + selection); /* * Broadcast the selection (which may be empty, if the user just cleared all @@ -941,6 +914,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel return loadNotifiesHandled; } + @Override public void setJalviewColourScheme(ColourSchemeI cs) { colourBySequence = false; @@ -957,12 +931,15 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel List residueSet = ResidueProperties.getResidues(isNucleotide(), false); - for (String res : residueSet) + for (String resName : residueSet) { - Color col = cs.findColour(res.charAt(0)); + char res = resName.length() == 3 + ? ResidueProperties.getSingleCharacterCode(resName) + : resName.charAt(0); + Color col = cs.findColour(res, 0, null, null, 0f); command.append("color " + col.getRed() / normalise + "," - + col.getGreen() / normalise + "," + col.getBlue() - / normalise + " ::" + res + ";"); + + col.getGreen() / normalise + "," + col.getBlue() / normalise + + " ::" + resName + ";"); } sendAsynchronousCommand(command.toString(), COLOURING_CHIMERA); @@ -1008,18 +985,19 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel /** * Send the Chimera 'background solid " command. * - * @see https + * @see https * ://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/background * .html * @param col */ + @Override public void setBackgroundColour(Color col) { viewerCommandHistory(false); double normalise = 255D; final String command = "background solid " + col.getRed() / normalise - + "," + col.getGreen() / normalise + "," + col.getBlue() - / normalise + ";"; + + "," + col.getGreen() / normalise + "," + + col.getBlue() / normalise + ";"; viewer.sendChimeraCommand(command, false); viewerCommandHistory(true); } @@ -1070,27 +1048,10 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * * @return */ + @Override public List getChainNames() { - List names = new ArrayList(); - String[][] allNames = getChains(); - if (allNames != null) - { - for (String[] chainsForPdb : allNames) - { - if (chainsForPdb != null) - { - for (String chain : chainsForPdb) - { - if (chain != null && !names.contains(chain)) - { - names.add(chain); - } - } - } - } - } - return names; + return chainNames; } /** @@ -1134,30 +1095,22 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel * features visible in Jalview * * @param avp + * @return */ - public void sendFeaturesToViewer(AlignmentViewPanel avp) + public int sendFeaturesToViewer(AlignmentViewPanel avp) { // TODO refactor as required to pull up to an interface AlignmentI alignment = avp.getAlignment(); - FeatureRenderer fr = getFeatureRenderer(avp); - - /* - * fr is null if feature display is turned off - */ - if (fr == null) - { - return; - } - String[] files = getPdbFile(); + String[] files = getStructureFiles(); if (files == null) { - return; + return 0; } StructureMappingcommandSet commandSet = ChimeraCommands .getSetAttributeCommandsForFeatures(getSsm(), files, - getSequence(), fr, alignment); + getSequence(), avp); String[] commands = commandSet.commands; if (commands.length > 10) { @@ -1170,6 +1123,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel sendAsynchronousCommand(command, null); } } + return commands.length; } /** @@ -1196,9 +1150,8 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel sendAsynchronousCommand("open cmd:" + path, null); } catch (IOException e) { - System.err - .println("Sending commands to Chimera via file failed with " - + e.getMessage()); + System.err.println("Sending commands to Chimera via file failed with " + + e.getMessage()); } } @@ -1225,36 +1178,31 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel String cmd = "list residues attr '" + attName + "'"; List residues = sendChimeraCommand(cmd, true); - /* - * TODO check if Jalview already has this feature name, if so give it a - * distinguishing prefix e.g. chim_ - */ - FeatureRenderer fr = alignmentPanel.getFeatureRenderer(); - // todo should getRenderOrder be in api.FeatureRenderer? - // FIXME this is empty if feature display is turned off - List existingFeatures = ((jalview.gui.FeatureRenderer) fr) - .getRenderOrder(); - if (existingFeatures.contains(attName)) + boolean featureAdded = createFeaturesForAttributes(attName, residues); + if (featureAdded) { - // TODO check if feature of this name is in group Chimera - // if so don't create a new feature name! - // problem: needs a lookup of features for feature group - attName = getStructureFeaturePrefix() + attName; + alignmentPanel.getFeatureRenderer().featuresAdded(); } + } - /* - * Expect 0, 1 or more reply lines of the format (chi2 is attName): - * residue id #0:5.A chi2 -155.000836316 index 5 - * or - * residue id #0:6.A chi3 None - * - * We assume here that attributes on structure do not naturally convert - * to ranges on sequence, i.e. we just set one feature per mapped position. - * - * To conflate positions, would need to first build a map - * Map>> - * and then traverse it to find feature ranges. - */ + /** + * Create features in Jalview for the given attribute name and structure + * residues. + * + *
    +   * The residue list should be 0, 1 or more reply lines of the format: 
    +   *     residue id #0:5.A isHelix -155.000836316 index 5 
    +   * or 
    +   *     residue id #0:6.A isHelix None
    +   * 
    + * + * @param attName + * @param residues + * @return + */ + protected boolean createFeaturesForAttributes(String attName, + List residues) + { boolean featureAdded = false; String featureGroup = getViewerFeatureGroup(); @@ -1303,14 +1251,18 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel spec.setPdbFile(pdbFile); List atoms = Collections.singletonList(spec); - SearchResults sr = getSsm() + + /* + * locate the mapped position in the alignment (if any) + */ + SearchResultsI sr = getSsm() .findAlignmentPositionsForStructurePositions(atoms); /* * expect one matched alignment position, or none * (if the structure position is not mapped) */ - for (Match m : sr.getResults()) + for (SearchResultMatchI m : sr.getResults()) { SequenceI seq = m.getSequence(); int start = m.getStart(); @@ -1322,22 +1274,7 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel featureAdded |= seq.addSequenceFeature(sf); } } - if (featureAdded) - { - fr.featuresAdded(); - } - } - - /** - * Answers a 'namespace' prefix to use for features created in Jalview from - * attributes in the structure viewer - * - * @return - */ - protected String getStructureFeaturePrefix() - { - // TODO pull up as abstract - return CHIMERA_FEATURE_PREFIX; + return featureAdded; } /** @@ -1351,4 +1288,24 @@ public abstract class JalviewChimeraBinding extends AAStructureBindingModel // todo pull up to interface return CHIMERA_FEATURE_GROUP; } + + public Hashtable getChainFile() + { + return chainFile; + } + + public List getChimeraModelByChain(String chain) + { + return chimeraMaps.get(chainFile.get(chain)); + } + + public int getModelNoForChain(String chain) + { + List foundModels = getChimeraModelByChain(chain); + if (foundModels != null && !foundModels.isEmpty()) + { + return foundModels.get(0).getModelNumber(); + } + return -1; + } }