package org.nexml.model.impl; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.nexml.model.Character; import org.nexml.model.Matrix; import org.nexml.model.MatrixCell; import org.nexml.model.OTU; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NodeList; abstract class MatrixImpl<T> extends OTUsLinkableImpl<Character> implements Matrix<T> { private Element mFormatElement; private Element mMatrixElement; private String mType; /** * Protected constructors that take a DOM document object but not * an element object are used for generating new element nodes in * a NeXML document. On calling such constructors, a new element * is created, which can be retrieved using getElement(). After this * step, the Impl class that called this constructor would still * need to attach the element in the proper location (typically * as a child element of the class that called the constructor). * @param document a DOM document object * @author rvosa */ protected MatrixImpl(Document document) { super(document); } protected MatrixImpl(Document document,String type) { super(document); setType(type); } /** * Protected constructors are intended for recursive parsing, i.e. * starting from the root element (which maps onto DocumentImpl) we * traverse the element tree such that for every child element that maps * onto an Impl class the containing class calls that child's protected * constructor, passes in the element of the child. From there the * child takes over, populates itself and calls the protected * constructors of its children. These should probably be protected * because there is all sorts of opportunity for outsiders to call * these in the wrong context, passing in the wrong elements etc. * @param document the containing DOM document object. Every Impl * class needs a reference to this so that it can create DOM element * objects * @param element the equivalent NeXML element (e.g. for OTUsImpl, it's * the <otus/> element) * @author rvosa */ protected MatrixImpl(Document document,Element element) { super(document,element); } private final Map<OTU, Map<Character, MatrixCellImpl<T>>> mMatrixCells = new HashMap<OTU, Map<Character, MatrixCellImpl<T>>>(); private boolean mCompact; /* * (non-Javadoc) * @see org.nexml.model.impl.NexmlWritableImpl#getTagName() */ @Override String getTagName() { return getTagNameClass(); } static String getTagNameClass() { return "characters"; } /* * (non-Javadoc) * @see org.nexml.model.Matrix#getColumn(org.nexml.model.Character) */ public List<MatrixCell<T>> getColumn(Character character) { List<MatrixCell<T>> column = new ArrayList<MatrixCell<T>>(); for (Map<Character, MatrixCellImpl<T>> characterToMatrixCell : mMatrixCells .values()) { column.add(characterToMatrixCell.get(character)); } return column; } /* * (non-Javadoc) * @see org.nexml.model.Matrix#getRow(org.nexml.model.OTU) */ public List<MatrixCell<T>> getRow(OTU otu) { Map<Character, MatrixCellImpl<T>> charsToCells = mMatrixCells.get(otu); List<MatrixCell<T>> row = new ArrayList<MatrixCell<T>>(); for (Character character : getThings()) { row.add(charsToCells.get(character)); } return row; } /** * This method is necessary because cell elements actually * go inside row elements, which are linked to otu elements * by id references. XXX This implementation is inefficient * because it scans the matrix elements for child row elements * with the otu's id. If none is found, a new one is created. * There are at least two better implementations: i) we * do create Row objects; ii) we do the lookup through a * HashMap @author rvosa * @param otu * @return */ protected Element getRowElement(OTU otu) { Element rowElement = null; NodeList rows = getMatrixElement().getElementsByTagName("row"); for ( int i = 0; i < rows.getLength(); i++ ) { if ( ((Element)rows.item(i)).getAttribute("otu").equals(otu.getId()) ) { rowElement = (Element)rows.item(i); } } if ( null == rowElement ) { rowElement = getDocument().createElement("row"); rowElement.setAttribute("otu", otu.getId()); identify(rowElement,true); getMatrixElement().appendChild(rowElement); } return rowElement; } /** * This method creates a lot out of thin air: * - if no matrix element exists, it is created * - if no row element for otu exists, it is created * - if no cell element for otu and char exists, it is created * @author rvosa */ public MatrixCell<T> getCell(OTU otu, Character character) { if (!mMatrixCells.containsKey(otu)) { mMatrixCells.put(otu, new HashMap<Character, MatrixCellImpl<T>>()); } MatrixCellImpl<T> matrixCell = mMatrixCells.get(otu).get(character); if (null == matrixCell) { matrixCell = new MatrixCellImpl<T>(getDocument()); if ( null == getMatrixElement() ) { setMatrixElement( getDocument().createElement("matrix") ); getElement().appendChild( getMatrixElement() ); } getRowElement(otu).appendChild(matrixCell.getElement()); matrixCell.getElement().setAttribute("char", character.getId()); matrixCell.getElement().removeAttribute("id"); Map<Character, MatrixCellImpl<T>> row = mMatrixCells.get(otu); row.put(character, matrixCell); } return matrixCell; } protected void setCell(OTU otu, Character character, MatrixCellImpl<T> matrixCell) { if (!mMatrixCells.containsKey(otu)) { mMatrixCells.put(otu, new HashMap<Character, MatrixCellImpl<T>>()); } mMatrixCells.get(otu).put(character, matrixCell); } protected MatrixCell<T> createMatrixCell(OTU otu,Character character,Element element) { MatrixCellImpl<T> cell = new MatrixCellImpl<T>(getDocument(),element); this.setCell(otu, character, cell); return cell; } protected Element getFormatElement() { if ( null == mFormatElement ) { List<Element> formatElements = getChildrenByTagName(getElement(), "format"); if ( formatElements.isEmpty() ) { Element format = getDocument().createElement("format"); getElement().insertBefore(format, getMatrixElement()); setFormatElement(format); } else if ( formatElements.size() == 1 ) { setFormatElement(formatElements.get(0)); } else { throw new RuntimeException("Too many format elements"); } } return mFormatElement; } protected void setFormatElement(Element formatElement) { mFormatElement = formatElement; } protected Element getMatrixElement() { if ( null == mMatrixElement ) { List<Element> matrixElements = getChildrenByTagName(getElement(), "matrix"); if ( matrixElements.isEmpty() ) { Element matrix = getDocument().createElement("matrix"); getElement().appendChild(matrix); setMatrixElement(matrix); } else if ( matrixElements.size() == 1 ) { setMatrixElement(matrixElements.get(0)); } else { throw new RuntimeException("Too many matrix elements"); } } return mMatrixElement; } protected void setMatrixElement(Element matrixElement) { mMatrixElement = matrixElement; } protected Character getCharacterByIndex(int i) { return getThings().get(i); } /* * (non-Javadoc) * @see org.nexml.model.Matrix#removeCharacter(org.nexml.model.Character) */ public void removeCharacter(Character character) { removeThing(character); for (Map<Character, MatrixCellImpl<T>> characterToMatrixCell : mMatrixCells .values()) { characterToMatrixCell.remove(character); } } /* * (non-Javadoc) * @see org.nexml.model.Matrix#getCharacters() */ public List<Character> getCharacters() { return getThings(); } /* * (non-Javadoc) * @see org.nexml.model.Matrix#setSeq(java.lang.String, org.nexml.model.OTU) */ public void setSeq(String seq,OTU otu) { // check if we're switching types if ( ! mCompact ) { setType(getType(),true); System.err.println("Warning: unchecked conversion from Cells to Seqs matrix - do this only on matrices w/o cells"); } Element row = getRowElement(otu); // remove all old seq and cell elements (if any), // but keep any other old nodes List<Element> oldElements = getChildrenByTagName(row, "cell"); oldElements.addAll(getChildrenByTagName(row, "seq")); for ( Element oldElement : oldElements ) { row.removeChild(oldElement); } // then, create a new seq child element Element seqElement = getDocument().createElement("seq"); seqElement.setTextContent(seq); row.appendChild(seqElement); } protected String getType() { return mType; } protected void setType(String type, boolean compact) { mType = type; mCompact = compact; String subType = compact ? "Seqs" : "Cells"; getElement().setAttributeNS(XSI_URI, XSI_TYPE, NEX_PRE+":"+type+subType ); } protected void setType(String type) { setType(type,false); } public int getSegmentCount() { return getCharacters().size(); } public Character getSegment(int index) { return getCharacters().get(index); } }