/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * * Created on 26.04.2004 * @author Andreas Prlic * */ package org.biojava.nbio.structure; import java.io.Serializable; import java.util.ArrayList; import java.util.List; import java.util.ListIterator; import org.biojava.nbio.structure.io.FileConvert; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Implementation of a PDB Structure. This class * provides the data contained in a PDB file. * to get structure objects from different sources * see io package. * * @author Andreas Prlic * @author Jules Jacobsen * @since 1.4 * @version %I% %G% */ public class StructureImpl implements Structure, Serializable { private static final long serialVersionUID = -8344837138032851348L; private static final Logger logger = LoggerFactory.getLogger(StructureImpl.class); private String pdb_id ; private List<Model> models; private List<EntityInfo> entityInfos; private List<DBRef> dbrefs; private List<Bond> ssbonds; private List<Site> sites; private String name ; private StructureIdentifier structureIdentifier; private PDBHeader pdbHeader; private Long id; private boolean biologicalAssembly; /** * Constructs a StructureImpl object. */ public StructureImpl() { super(); models = new ArrayList<>(); name = ""; entityInfos = new ArrayList<>(); dbrefs = new ArrayList<>(); pdbHeader = new PDBHeader(); ssbonds = new ArrayList<>(); sites = new ArrayList<>(); } /** get the ID used by Hibernate * * @return the ID used by Hibernate */ @Override public Long getId() { return id; } /** set the ID used by Hibernate * * @param id the hibernate ID */ @Override public void setId(Long id) { this.id = id; } /** Construct a Structure object that only contains a single group * * @param g group object */ public StructureImpl(Group g){ this(); Chain c = new ChainImpl(); c.addGroup(g); addChain(c); } /** construct a Structure object that contains a particular chain * * @param c chain */ public StructureImpl(Chain c){ this(); addChain(c); } /** returns an identical copy of this structure . * @return an identical Structure object */ @Override public Structure clone() { // Note: structures are also cloned in SubstructureIdentifier.reduce(). // Changes might need to be made there as well Structure n = new StructureImpl(); // go through whole substructure and clone ... // copy structure data n.setPDBCode(getPDBCode()); n.setName(getName()); //TODO the header data is not being deep-copied, that's a minor issue since it is just some static metadata, but we should recheck this if needed - JD 2014-12-11 n.setPDBHeader(pdbHeader); n.setDBRefs(this.getDBRefs()); n.setSites(getSites()); // go through each chain and clone chain for (int i=0;i<nrModels();i++){ List<Chain> cloned_model = new ArrayList<Chain>(); for (int j=0;j<size(i);j++){ Chain cloned_chain = (Chain) getChainByIndex(i,j).clone(); // setting the parent: can only be done from the parent cloned_chain.setStructure(n); cloned_model.add(cloned_chain); } n.addModel(cloned_model); } // deep-copying of entityInfofos is tricky: there's cross references also in the Chains // beware: if we copy the entityInfos we would also need to reset the references to entityInfos in the individual chains List<EntityInfo> newEntityInfoList = new ArrayList<EntityInfo>(); for (EntityInfo entityInfo : this.entityInfos) { EntityInfo newEntityInfo = new EntityInfo(entityInfo); // this sets everything but the chains for (String asymId:entityInfo.getChainIds()) { for (int modelNr=0;modelNr<n.nrModels();modelNr++) { Chain newChain = n.getChain(asymId,modelNr); if (newChain==null) { // this actually happens for structure 1msh, which has no chain B for model 29 (clearly a deposition error) logger.warn("Could not find chain asymId "+asymId+" of model "+modelNr+" while cloning entityInfo "+entityInfo.getMolId()+". Something is wrong!"); continue; } newChain.setEntityInfo(newEntityInfo); newEntityInfo.addChain(newChain); } } newEntityInfoList.add(newEntityInfo); } n.setEntityInfos(newEntityInfoList); // TODO ssbonds are complicated to clone: there are deep references inside Atom objects, how would we do it? - JD 2016-03-03 return n ; } /** {@inheritDoc} */ @Override public Group findGroup(String chainName, String pdbResnum, int modelnr) throws StructureException { // if structure is xray there will be only one "model". if ( modelnr > models.size()) throw new StructureException(" no model nr " + modelnr + " in this structure. (contains "+models.size()+")"); // first we need to gather all groups with the author id chainName: polymers, non-polymers and waters Chain polyChain = getPolyChainByPDB(chainName, modelnr); if(polyChain != null) { List<Group> groups = new ArrayList<>(); groups.addAll(polyChain.getAtomGroups()); // there can be more than one non-poly chain for a given author id for (Chain chain: getNonPolyChainsByPDB(chainName, modelnr)) { groups.addAll(chain.getAtomGroups()); } Chain water = getWaterChainByPDB(chainName, modelnr); if (water!=null) groups.addAll(water.getAtomGroups()); // now iterate over all groups // in order to find the amino acid that has this pdbRenum. for (Group g : groups) { String rnum = g.getResidueNumber().toString(); //System.out.println(g + " >" + rnum + "< >" + pdbResnum + "<"); // we only mutate amino acids // and ignore hetatoms and nucleotides in this case if (rnum.equals(pdbResnum)) { return g; } } } throw new StructureException("could not find group " + pdbResnum + " in chain " + chainName); } /** {@inheritDoc} */ @Override public Group findGroup(String chainName, String pdbResnum) throws StructureException { return findGroup(chainName, pdbResnum, 0); } /** {@inheritDoc} */ @Override public Chain findChain(String chainName, int modelnr) throws StructureException { return getChainByPDB(chainName, modelnr); } /** {@inheritDoc} */ @Override public Chain findChain(String chainId) throws StructureException { return findChain(chainId,0); } /** {@inheritDoc} */ @Override public void setPDBCode (String pdb_id_) { pdb_id = pdb_id_ ; } /** {@inheritDoc} */ @Override public String getPDBCode () { return pdb_id ; } /** {@inheritDoc} */ @Override public void setName(String nam) { name = nam; } /** {@inheritDoc} */ @Override public String getName() { return name; } /** * @return The StructureIdentifier used to create this structure */ @Override public StructureIdentifier getStructureIdentifier() { return structureIdentifier; } /** * @param structureIdentifier the structureIdentifier corresponding to this structure */ @Override public void setStructureIdentifier(StructureIdentifier structureIdentifier) { this.structureIdentifier = structureIdentifier; } /** {@inheritDoc} */ @Override public void addChain(Chain chain) { int modelnr = 0 ; addChain(chain,modelnr); } /** {@inheritDoc} */ @Override public void addChain(Chain chain, int modelnr) { // if model has not been initialized, init it! chain.setStructure(this); if (models.isEmpty()) { Model model = new Model(); List<Chain> modelChains = new ArrayList<>() ; modelChains.add(chain); model.setChains(modelChains); models.add(model); } else { Model model = models.get(modelnr); model.addChain(chain); } } /** {@inheritDoc} */ @Override public Chain getChainByIndex(int number) { int modelnr = 0 ; return getChainByIndex(modelnr,number); } /** {@inheritDoc} */ @Override public Chain getChainByIndex(int modelnr,int number) { Model model = models.get(modelnr); return model.getChains().get(number); } /** {@inheritDoc} */ @Override public void addModel(List<Chain> modelChains){ for (Chain c: modelChains){ c.setStructure(this); } Model model = new Model(); model.setChains(modelChains); models.add(model); } /** {@inheritDoc} */ @Override public void setChains(List<Chain> chains){ setModel(0,chains); } /** {@inheritDoc} */ @Override public void setModel(int position, List<Chain> modelChains){ if (modelChains == null) throw new IllegalArgumentException("trying to set model to null!"); for (Chain c: modelChains) c.setStructure(this); //System.out.println("model size:" + models.size()); Model model = new Model(); model.setChains(modelChains); if (models.isEmpty()){ models.add(model); } else { models.set(position, model); } } /** String representation. * */ @Override public String toString(){ String newline = System.getProperty("line.separator"); StringBuilder str = new StringBuilder(); str.append("structure "); str.append(name); str.append(" "); str.append(pdb_id); str.append(" "); if ( nrModels()>1 ){ str.append( " models: "); str.append(nrModels()); str.append(newline) ; } str.append(pdbHeader); str.append(newline) ; for (int i=0;i<nrModels();i++){ if ( nrModels()>1 ) { str.append(" model["); str.append(i); str.append("]:"); str.append(newline); } str.append(" chains:"); str.append(newline); for (int j=0;j<size(i);j++){ Chain cha = getChainByIndex(i,j); List<Group> agr = cha.getAtomGroups(GroupType.AMINOACID); List<Group> hgr = cha.getAtomGroups(GroupType.HETATM); List<Group> ngr = cha.getAtomGroups(GroupType.NUCLEOTIDE); str.append("chain ") .append(j).append(": asymId:") .append(cha.getId()) .append(" authId:") .append(cha.getName()).append(" "); if ( cha.getEntityInfo() != null){ EntityInfo comp = cha.getEntityInfo(); String molName = comp.getDescription(); if ( molName != null){ str.append(molName); } String type = comp.getType().toString(); str.append(" (") .append(type) .append(")"); } str.append(newline); str.append(" length SEQRES: ").append(cha.getSeqResLength()); str.append(" length ATOM: ").append(cha.getAtomLength()); str.append(" aminos: ").append(agr.size()); str.append(" hetatms: ").append(hgr.size()); str.append(" nucleotides: ").append(ngr.size()).append(newline); } } str.append("DBRefs: ").append(dbrefs.size()).append(newline); for (DBRef dbref: dbrefs){ str.append(dbref.toPDB()).append(newline); } str.append("Molecules: ").append(newline); for (EntityInfo mol : entityInfos) { str.append(mol).append(newline); } return str.toString() ; } @Override public int size() { int modelnr = 0 ; if (!models.isEmpty()) { return models.get(modelnr).getPolyChains().size(); } else { return 0 ; } } /** return number of chains of model. * */ @Override public int size(int modelnr) { return models.get(modelnr).size(); } // some NMR stuff : /** return number of models. */ @Override public int nrModels() { return models.size() ; } /** * Whether this Structure is a crystallographic structure or not. * It will first check the experimental technique and if not present it will try * to guess from the presence of a space group and sensible cell parameters * * @return true if crystallographic, false otherwise */ @Override public boolean isCrystallographic() { if (pdbHeader.getExperimentalTechniques()!=null) { return ExperimentalTechnique.isCrystallographic(pdbHeader.getExperimentalTechniques()); } else { // no experimental technique known, we try to guess... if (pdbHeader.getCrystallographicInfo().getSpaceGroup()!=null) { // space group defined but no crystal cell: incomplete info, return false return pdbHeader.getCrystallographicInfo().getCrystalCell() != null && pdbHeader.getCrystallographicInfo().getCrystalCell().isCellReasonable(); } } return false; } /** * Whether this Structure is a NMR structure or not. * It will first check the experimental technique and if not present it will try * to guess from the presence of more than 1 model and from b-factors being 0 in first chain of first model * @return true if NMR, false otherwise */ @Override public boolean isNmr() { // old implementation was: //return nmrflag; if (pdbHeader.getExperimentalTechniques()!=null) { return ExperimentalTechnique.isNmr(pdbHeader.getExperimentalTechniques()); } else { // no experimental technique known, we try to guess... if (nrModels()>1) { if (pdbHeader.getCrystallographicInfo().getSpaceGroup()!=null) { // multimodel, sg defined, but missing cell: must be NMR if (pdbHeader.getCrystallographicInfo().getCrystalCell()==null) return true; // multi-model, sg defined and cell unreasonable: must be NMR if (!pdbHeader.getCrystallographicInfo().getCrystalCell().isCellReasonable()) return true; } else { // multi-model and missing space group: must be NMR return true; } } } return false; } /** {@inheritDoc} */ @Override public List<Chain> getChains(int modelIdx){ return getModel(modelIdx); } /** {@inheritDoc} */ @Override public List<Chain> getChains(){ if (models.size()==0) { return new ArrayList<>(0); } return getChains(0); } @Override public List<Chain> getPolyChains() { if (models.size()==0) { return new ArrayList<>(0); } return getPolyChains(0); } @Override public List<Chain> getPolyChains(int modelIdx) { return models.get(modelIdx).getPolyChains(); } @Override public List<Chain> getNonPolyChains() { if (models.size()==0) { return new ArrayList<>(0); } return getNonPolyChains(0); } @Override public List<Chain> getNonPolyChains(int modelIdx) { return models.get(modelIdx).getNonPolyChains(); } @Override public List<Chain> getWaterChains() { if (models.size()==0) { return new ArrayList<>(0); } return getWaterChains(0); } @Override public List<Chain> getWaterChains(int modelIdx) { return models.get(modelIdx).getWaterChains(); } /** {@inheritDoc} */ @Override public void setChains(int modelnr, List<Chain> chains){ for (Chain c: chains){ c.setStructure(this); } if (models.size()>modelnr) { models.remove(modelnr); } Model model = new Model(); model.setChains(chains); models.add(modelnr, model); } /** Retrieve all Chains belonging to a model . * * @param modelnr an int * @return a List object */ @Override public List<Chain> getModel(int modelnr) { return models.get(modelnr).getChains(); } /** {@inheritDoc} */ @Override public Chain getChainByPDB(String authId, int modelnr) throws StructureException{ Chain c = getPolyChainByPDB(authId, modelnr); if (c==null ) { throw new StructureException("Could not find chain with authId \"" + authId + "\"" + " for PDB id " + pdb_id + ", model "+modelnr); } return c; } /** {@inheritDoc} */ @Override public Chain getChain(String asymId, int modelnr) { List<Chain> chains = getChains(modelnr); for (Chain c : chains) { if (c.getId().equals(asymId)) { return c; } } return null; } /** {@inheritDoc} */ @Override public Chain getChain(String asymId) { return getChain(asymId,0); } /** {@inheritDoc} */ @Override public Chain getChainByPDB(String chainId) throws StructureException{ if(nrModels() < 1 ) { throw new StructureException("No chains are present."); } return getChainByPDB(chainId,0); } @Override public Chain getPolyChain(String asymId) { return getPolyChain(asymId, 0); } @Override public Chain getPolyChain(String asymId, int modelIdx) { Model model = models.get(modelIdx); if (model==null) { return null; } List<Chain> polyChains = model.getPolyChains(); for (Chain c : polyChains){ if (c.getId().equals(asymId)) return c; } return null; } @Override public Chain getNonPolyChain(String asymId) { return getNonPolyChain(asymId, 0); } @Override public Chain getNonPolyChain(String asymId, int modelIdx) { Model model = models.get(modelIdx); if (model==null) { return null; } List<Chain> nonpolyChains = model.getNonPolyChains(); for (Chain c : nonpolyChains){ if (c.getId().equals(asymId)) return c; } return null; } @Override public Chain getPolyChainByPDB(String authId) { return getPolyChainByPDB(authId, 0); } @Override public Chain getPolyChainByPDB(String authId, int modelIdx) { Model model = models.get(modelIdx); if (model==null) { return null; } List<Chain> polyChains = model.getPolyChains(); for (Chain c : polyChains){ if (c.getName().equals(authId)) return c; } return null; } @Override public List<Chain> getNonPolyChainsByPDB(String authId) { return getNonPolyChainsByPDB(authId, 0); } @Override public List<Chain> getNonPolyChainsByPDB(String authId, int modelIdx) { List<Chain> chains = new ArrayList<>(); Model model = models.get(modelIdx); if (model==null) { return chains; } List<Chain> nonpolyChains = model.getNonPolyChains(); for (Chain c : nonpolyChains){ if (c.getName().equals(authId)) chains.add(c); } return chains; } @Override public Chain getWaterChain(String asymId) { return getWaterChain(asymId, 0); } @Override public Chain getWaterChain(String asymId, int modelIdx) { Model model = models.get(modelIdx); if (model==null) { return null; } List<Chain> waterChains = model.getWaterChains(); for (Chain c : waterChains){ if (c.getId().equals(asymId)) return c; } return null; } @Override public Chain getWaterChainByPDB(String authId) { return getWaterChainByPDB(authId, 0); } @Override public Chain getWaterChainByPDB(String authId, int modelIdx) { Model model = models.get(modelIdx); if (model==null) { return null; } List<Chain> waterChains = model.getWaterChains(); for (Chain c : waterChains){ if (c.getName().equals(authId)) return c; } return null; } /** {@inheritDoc} */ @Override public String toPDB() { FileConvert f = new FileConvert(this) ; return f.toPDB(); } /** {@inheritDoc} */ @Override public String toMMCIF() { FileConvert f = new FileConvert(this); return f.toMMCIF(); } /** {@inheritDoc} */ @Override public boolean hasChain(String authId) { int modelnr = 0; List<Chain> chains = getChains(modelnr); for (Chain c : chains) { // we check here with equals because we might want to distinguish between upper and lower case chains! if (c.getId().equals(authId)) { return true; } } return false; } /** {@inheritDoc} */ @Override public boolean hasNonPolyChain(String asymId){ int modelnr = 0; List<Chain> chains = models.get(modelnr).getNonPolyChains(); for (Chain c : chains) { // we check here with equals because we might want to distinguish between upper and lower case chains! if (c.getId().equals(asymId)) { return true; } } return false; } /** {@inheritDoc} */ @Override public boolean hasPdbChain(String authId) { int modelnr = 0; List<Chain> chains = getChains(modelnr); for (Chain c : chains) { // we check here with equals because we might want to distinguish between upper and lower case chains! if (c.getName().equals(authId)) { return true; } } return false; } /** {@inheritDoc} */ @Override public void setEntityInfos(List<EntityInfo> molList){ this.entityInfos = molList; } /** {@inheritDoc} */ @Override public void addEntityInfo(EntityInfo entityInfo) { this.entityInfos.add(entityInfo); } /** {@inheritDoc} */ @Override public List<EntityInfo> getEntityInfos() { return entityInfos; } /** {@inheritDoc} */ @Override public EntityInfo getCompoundById(int molId) { return getEntityById(molId); } /** {@inheritDoc} */ @Override public EntityInfo getEntityById(int entityId) { for (EntityInfo mol : this.entityInfos){ if (mol.getMolId()==entityId){ return mol; } } return null; } /** {@inheritDoc} */ @Override public List<DBRef> getDBRefs() { return dbrefs; } /** {@inheritDoc} */ @Override public void setDBRefs(List<DBRef> dbrefs) { if ( dbrefs == null) throw new IllegalArgumentException("trying to set dbrefs to null!"); for( DBRef ref : dbrefs){ ref.setParent(this); } this.dbrefs = dbrefs; } /** {@inheritDoc} */ @Override public PDBHeader getPDBHeader() { return pdbHeader; } /** {@inheritDoc} */ @Override public void setPDBHeader(PDBHeader pdbHeader){ this.pdbHeader = pdbHeader; } /** {@inheritDoc} */ @Override public List<Bond> getSSBonds(){ return ssbonds; } /** {@inheritDoc} */ @Override public void setSSBonds(List<Bond> ssbonds){ this.ssbonds = ssbonds; } /** * Adds a single disulfide Bond to this structure * * @param ssbond the SSBond. */ @Override public void addSSBond(Bond ssbond){ ssbonds.add(ssbond); } /** * Return whether or not the entry has an associated journal article * or publication. The JRNL section is not mandatory and thus may not be * present. * @return flag if a JournalArticle could be found. */ @Override public boolean hasJournalArticle() { return this.pdbHeader.hasJournalArticle(); } /** * get the associated publication as defined by the JRNL records in a PDB * file. * @return a JournalArticle */ @Override public JournalArticle getJournalArticle() { return this.pdbHeader.getJournalArticle(); } /** * set the associated publication as defined by the JRNL records in a PDB * file. * @param journalArticle the article */ @Override public void setJournalArticle(JournalArticle journalArticle) { this.pdbHeader.setJournalArticle(journalArticle); } /** * @return the sites contained in this structure */ @Override public List<Site> getSites() { return sites; } /** * @param sites the sites to set in the structure */ @Override public void setSites(List<Site> sites) { this.sites = sites; } /** Caution: we should probably remove this to avoid confusion. Currently this is always an empty list! * * @return a list of Groups listed in the HET records - this will not * include any waters. */ /** * Sets a flag to indicate if this structure is a biological assembly * @param biologicalAssembly true if biological assembly, otherwise false * @since 3.2 */ @Override public void setBiologicalAssembly(boolean biologicalAssembly) { this.biologicalAssembly = biologicalAssembly; } /** * Gets flag that indicates if this structure is a biological assembly * @return the sites contained in this structure * @since 3.2 */ @Override public boolean isBiologicalAssembly() { return biologicalAssembly; } /** * Sets crystallographic information for this structure * @param crystallographicInfo crystallographic information * @since 3.2 */ @Override public void setCrystallographicInfo(PDBCrystallographicInfo crystallographicInfo) { this.pdbHeader.setCrystallographicInfo(crystallographicInfo); } /** * Gets crystallographic information for this structure * @return PDBCrystallographicInfo crystallographic information * @since 3.2 */ @Override public PDBCrystallographicInfo getCrystallographicInfo() { return pdbHeader.getCrystallographicInfo(); } /** {@inheritDoc} */ @Override public String getIdentifier() { //1. StructureIdentifier if(getStructureIdentifier() != null) { return getStructureIdentifier().getIdentifier(); } //2. Name if(getName() != null) { return getName(); } //3. PDBCode + ranges return toCanonical().getIdentifier(); } /** {@inheritDoc} */ @Deprecated @Override public String getPdbId() { return pdb_id; } /** {@inheritDoc} */ @Override public void resetModels() { models = new ArrayList<>(); } /** {@inheritDoc} */ @Deprecated @Override public List<ResidueRange> getResidueRanges() { return toCanonical().getResidueRanges(); } /** {@inheritDoc} */ @Deprecated @Override public List<String> getRanges() { return ResidueRange.toStrings(getResidueRanges()); } /** * Creates a SubstructureIdentifier based on the residues in this Structure. * * Only the first and last residues of each chain are considered, so chains * with gaps * @return A {@link SubstructureIdentifier} with residue ranges constructed from each chain */ private SubstructureIdentifier toCanonical() { StructureIdentifier real = getStructureIdentifier(); if(real != null) { try { return real.toCanonical(); } catch (StructureException e) { // generate fake one if needed } } // No identifier set, so generate based on residues present in the structure List<ResidueRange> range = new ArrayList<>(); for (Chain chain : getChains()) { List<Group> groups = chain.getAtomGroups(); ListIterator<Group> groupsIt = groups.listIterator(); if(!groupsIt.hasNext()) { continue; // no groups in chain } Group g = groupsIt.next(); ResidueNumber first = g.getResidueNumber(); //TODO Detect missing intermediate residues -sbliven, 2015-01-28 //Already better than previous whole-chain representation // get last residue while(groupsIt.hasNext()) { g = groupsIt.next(); } ResidueNumber last = g.getResidueNumber(); range.add(new ResidueRange(chain.getName(),first,last)); } return new SubstructureIdentifier(getPDBCode(),range); } }