edu.harvard.med.screensaver.service.cherrypicks.CherryPickRequestPlateMapFilesBuilder.java Source code

Java tutorial

Introduction

Here is the source code for edu.harvard.med.screensaver.service.cherrypicks.CherryPickRequestPlateMapFilesBuilder.java

Source

// $HeadURL$
// $Id$
//
// Copyright  2006, 2010, 2011, 2012 by the President and Fellows of Harvard College.
//
// Screensaver is an open-source project developed by the ICCB-L and NSRB labs
// at Harvard Medical School. This software is distributed under the terms of
// the GNU General Public License.

package edu.harvard.med.screensaver.service.cherrypicks;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Serializable;
import java.math.RoundingMode;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.apache.commons.collections.Factory;
import org.apache.commons.collections.MultiMap;
import org.apache.commons.collections.map.MultiValueMap;
import org.apache.log4j.Logger;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

import edu.harvard.med.screensaver.db.GenericEntityDAO;
import edu.harvard.med.screensaver.db.LibrariesDAO;
import edu.harvard.med.screensaver.model.VolumeUnit;
import edu.harvard.med.screensaver.model.cherrypicks.CherryPickAssayPlate;
import edu.harvard.med.screensaver.model.cherrypicks.CherryPickRequest;
import edu.harvard.med.screensaver.model.cherrypicks.LabCherryPick;
import edu.harvard.med.screensaver.model.cherrypicks.LabCherryPick.LabCherryPickStatus;
import edu.harvard.med.screensaver.model.libraries.Copy;
import edu.harvard.med.screensaver.model.libraries.Plate;
import edu.harvard.med.screensaver.model.libraries.PlateType;
import edu.harvard.med.screensaver.util.CSVPrintWriter;
import edu.harvard.med.screensaver.util.CustomNewlinePrintWriter;
import edu.harvard.med.screensaver.util.Pair;

/**
 * For a cherry pick request, builds the CSV files that define the assay plate
 * mapping.
 *
 * @author <a mailto="andrew_tolopko@hms.harvard.edu">Andrew Tolopko</a>
 * @author <a mailto="john_sullivan@hms.harvard.edu">John Sullivan</a>
 */
public class CherryPickRequestPlateMapFilesBuilder {

    /**
     * File extension for each plate map file in the zip file.
     * Stewart Rudnicki has indicated that this file extension should lower-case..
     */
    private static final String PLATE_MAP_FILE_EXTENSION = ".csv";

    /**
     * Windows newline must be used! The consumer of the files we're generating is
     * a machine in the lab that has this requirement, so cross-platform concerns
     * are not a concern.
     */
    private static final String NEWLINE = "\r\n";

    private static final String[] CSV_FILE_HEADERS = { "Source Plate", "Source Copy", "Source Well",
            "Source Plate Type", "Destination Well", "Destination Plate Type", "Person Visiting", "Screen Number",
            "Volume" };

    private static final String[] DISTINCT_PLATECOPYLIST_FILE_HEADERS = { "Source Plate", "Source Copy",
            "Location" };

    private static final String README_FILE_NAME = "README.txt";
    private static final String DISTINCT_PLATE_COPY_LIST_FILE_NAME = "plate-copy-location.csv";

    // static members

    private static Logger log = Logger.getLogger(CherryPickRequestPlateMapFilesBuilder.class);

    // instance data members

    private GenericEntityDAO genericEntityDao;
    private CherryPickRequestPlateMapper cherryPickRequestPlateMapper;
    private LibrariesDAO librariesDao;

    // public constructors and methods

    public CherryPickRequestPlateMapFilesBuilder(GenericEntityDAO dao, LibrariesDAO librariesDao,
            CherryPickRequestPlateMapper cherryPickRequestPlateMapper) {
        this.genericEntityDao = dao;
        this.librariesDao = librariesDao;
        this.cherryPickRequestPlateMapper = cherryPickRequestPlateMapper;
    }

    // public constructors and methods

    public InputStream buildZip(final CherryPickRequest cherryPickRequestIn, final Set<CherryPickAssayPlate> plates)
            throws IOException {
        CherryPickRequest cherryPickRequest = (CherryPickRequest) genericEntityDao
                .reattachEntity(cherryPickRequestIn);
        return doBuildZip(cherryPickRequest, plates);
    }

    // private methods

    @SuppressWarnings("unchecked")
    private InputStream doBuildZip(CherryPickRequest cherryPickRequest, Set<CherryPickAssayPlate> forPlates)
            throws IOException {
        ByteArrayOutputStream zipOutRaw = new ByteArrayOutputStream();
        ZipOutputStream zipOut = new ZipOutputStream(zipOutRaw);
        MultiMap/*<String,SortedSet<CherryPick>>*/ files2CherryPicks = buildCherryPickFiles(cherryPickRequest,
                forPlates);
        buildReadme(cherryPickRequest, zipOut);
        buildDistinctPlateCopyFile(cherryPickRequest, forPlates, zipOut);
        PrintWriter out = new CSVPrintWriter(new OutputStreamWriter(zipOut), NEWLINE);
        for (Iterator iter = files2CherryPicks.keySet().iterator(); iter.hasNext();) {
            String fileName = (String) iter.next();
            ZipEntry zipEntry = new ZipEntry(fileName);
            zipOut.putNextEntry(zipEntry);
            writeHeadersRow(out);
            for (LabCherryPick cherryPick : (SortedSet<LabCherryPick>) files2CherryPicks.get(fileName)) {
                writeCherryPickRow(out, cherryPick);
            }
            out.flush();
        }
        out.close();
        return new ByteArrayInputStream(zipOutRaw.toByteArray());
    }

    @SuppressWarnings("unchecked")
    private void buildReadme(CherryPickRequest cherryPickRequest, ZipOutputStream zipOut) throws IOException {

        ZipEntry zipEntry = new ZipEntry(README_FILE_NAME);
        zipOut.putNextEntry(zipEntry);
        PrintWriter writer = new CustomNewlinePrintWriter(zipOut, NEWLINE);

        writer.println("This zip file contains plate mappings for Cherry Pick Request "
                + cherryPickRequest.getCherryPickRequestNumber());
        writer.println();

        {
            StringBuilder buf = new StringBuilder();
            for (CherryPickAssayPlate assayPlate : cherryPickRequest.getActiveCherryPickAssayPlates()) {
                buf.append(assayPlate.getName()).append("\t").append(assayPlate.getStatusLabel());
                if (assayPlate.isPlatedAndScreened()) {
                    buf.append("\t(").append(assayPlate.getCherryPickScreenings().last().getDateOfActivity())
                            .append(" by ").append(assayPlate.getCherryPickScreenings().last().getPerformedBy()
                                    .getFullNameFirstLast())
                            .append(')');
                } else if (assayPlate.isPlated()) {
                    buf.append("\t(").append(assayPlate.getCherryPickLiquidTransfer().getDateOfActivity())
                            .append(" by ").append(assayPlate.getCherryPickLiquidTransfer().getPerformedBy()
                                    .getFullNameFirstLast())
                            .append(')');
                }
                buf.append(NEWLINE);
            }
            if (buf.length() > 0) {
                writer.println("Cherry pick plates:");
                writer.print(buf.toString());
                writer.println();
            }
        }

        Map<CherryPickAssayPlate, Integer> platesRequiringReload = cherryPickRequestPlateMapper
                .getAssayPlatesRequiringSourcePlateReload(cherryPickRequest);
        if (platesRequiringReload.size() > 0) {
            writer.println("WARNING: Some cherry pick plates will be created from the same source plate!");
            writer.println(
                    "You will need to reload one or more source plates for each of the following cherry pick plates:");
            for (CherryPickAssayPlate assayPlate : platesRequiringReload.keySet()) {
                writer.println("\tCherry pick plate '" + assayPlate.getName() + "' requires reload of source plate "
                        + platesRequiringReload.get(assayPlate));
            }
            writer.println();
        }

        {
            StringBuilder buf = new StringBuilder();
            MultiMap sourcePlateTypesForEachAssayPlate = getSourcePlateTypesForEachAssayPlate(cherryPickRequest);
            for (CherryPickAssayPlate assayPlate : cherryPickRequest.getActiveCherryPickAssayPlates()) {
                Set<PlateType> sourcePlateTypes = (Set<PlateType>) sourcePlateTypesForEachAssayPlate
                        .get(assayPlate.getName());
                if (sourcePlateTypes != null && sourcePlateTypes.size() > 1) {
                    buf.append(assayPlate.getName()).append(NEWLINE);
                }
            }
            if (buf.length() > 0) {
                writer.println(
                        "WARNING: Some cherry pick plates will be created from multiple source plates of non-uniform plate types!");
                writer.println("The following cherry pick plates are specified across multiple files:");
                writer.print(buf.toString());
                writer.println();
            }
        }

        writer.flush();
    }

    /**
     * for  [#3206] Generate a distinct plate/copy list for CPR download file
     */
    private void buildDistinctPlateCopyFile(CherryPickRequest cherryPickRequest,
            Set<CherryPickAssayPlate> forPlates, ZipOutputStream zipOut) throws IOException {
        PrintWriter out = new CSVPrintWriter(new OutputStreamWriter(zipOut), NEWLINE);
        zipOut.putNextEntry(new ZipEntry(DISTINCT_PLATE_COPY_LIST_FILE_NAME));
        for (String string : DISTINCT_PLATECOPYLIST_FILE_HEADERS) {
            out.print(string);
        }
        out.println();

        // TODO: consider caching this while building the CPR
        Set<Plate> sourcePlates = Sets.newHashSet();

        for (CherryPickAssayPlate plate : forPlates) {
            for (LabCherryPick lcp : plate.getLabCherryPicks()) {
                lcp = genericEntityDao.reloadEntity(lcp, true);
                sourcePlates.add(librariesDao.findPlate(lcp.getSourceWell().getPlateNumber(),
                        lcp.getSourceCopy().getName()));
            }
        }
        // sort by plate/copy/location
        List<Plate> list = Lists.newArrayList(sourcePlates);
        Collections.sort(list, new Comparator<Plate>() {
            @Override
            public int compare(Plate o1, Plate o2) {
                if (o1.equals(o2)) {
                    return o1.getLocation().compareTo(o2.getLocation());
                }
                return o1.compareTo(o2); // note Plate.compare() does a plate/copy compare
            }
        });
        for (Plate p : list) {
            out.print(p.getPlateNumber());
            out.print(p.getCopy().getName());
            out.print(p.getLocation() == null ? "" : p.getLocation().toDisplayString());
            out.println();
        }
    }

    @SuppressWarnings("unchecked")
    /**
     * Normally, we create 1 file per assay plate. However, in the case where an
     * assay plate is comprised of wells from library copy plates that have
     * different plate types, we need to generate a separate file for each source
     * plate type (i.e., the assay plate will be defined over multiple files).
     * @return a MultiMap that partitions the cherry picks by file,
     * ordering both the file names and cherry picks for each file.
     */
    private MultiMap/*<String,SortedSet<CherryPick>>*/ buildCherryPickFiles(CherryPickRequest cherryPickRequest,
            Set<CherryPickAssayPlate> forPlates) {
        MultiMap assayPlate2SourcePlateTypes = getSourcePlateTypesForEachAssayPlate(cherryPickRequest);

        MultiMap result = MultiValueMap.decorate(new TreeMap<String, SortedSet<LabCherryPick>>(), new Factory() {
            public Object create() {
                return new TreeSet<LabCherryPick>(PlateMappingCherryPickComparator.getInstance());
            }
        });

        // HACK: transform set of CPAP into a set of IDs, for purpose of checking
        // set membership; we can't rely upon CPAP.equals(), since we're comparing
        // non-managed entities with managed entities, and therefore we do not have
        // the guarantee of instance equality for entities with the same ID
        Set<Serializable> forPlateIds = new HashSet<Serializable>(forPlates.size());
        for (CherryPickAssayPlate cpap : forPlates) {
            if (cpap.getEntityId() == null) {
                throw new IllegalArgumentException(
                        "all members of 'forPlates' must already be persisted and have a database identifier");
            }
            forPlateIds.add(cpap.getEntityId());
        }

        for (LabCherryPick cherryPick : cherryPickRequest.getLabCherryPicks()) {
            if (cherryPick.isAllocated()) {
                CherryPickAssayPlate assayPlate = cherryPick.getAssayPlate();
                if (forPlates == null || (assayPlate != null && forPlateIds.contains(assayPlate.getEntityId()))) {
                    Set<PlateType> sourcePlateTypes = (Set<PlateType>) assayPlate2SourcePlateTypes
                            .get(assayPlate.getName());
                    String fileName = makeFilename(cherryPick, sourcePlateTypes.size());
                    result.put(fileName, cherryPick);
                }
            }
        }
        return result;
    }

    private String makeFilename(LabCherryPick cherryPick, int distinctSourcePlateTypes) {
        StringBuilder fileName = new StringBuilder(cherryPick.getAssayPlate().getName());

        if (distinctSourcePlateTypes > 1) {
            fileName.append(' ');
            fileName.append(cherryPick.getSourceCopy().findPlate(cherryPick.getSourceWell().getPlateNumber())
                    .getPlateType().toString());
        }

        int attempt = cherryPick.getAssayPlate().getAttemptOrdinal() + 1;
        if (attempt > 0) {
            fileName.append(" (Run").append(attempt).append(")");
        }

        fileName.append(PLATE_MAP_FILE_EXTENSION);

        return fileName.toString();
    }

    private MultiMap getSourcePlateTypesForEachAssayPlate(CherryPickRequest cherryPickRequest) {
        MultiMap assayPlateName2PlateTypes = MultiValueMap.decorate(new HashMap(), new Factory() {
            public Object create() {
                return new HashSet();
            }
        });

        for (LabCherryPick cherryPick : cherryPickRequest.getLabCherryPicks()) {
            if (cherryPick.isAllocated() && cherryPick.isMapped()) {
                assayPlateName2PlateTypes.put(cherryPick.getAssayPlate().getName(), cherryPick.getSourceCopy()
                        .findPlate(cherryPick.getSourceWell().getPlateNumber()).getPlateType());
            }
        }
        return assayPlateName2PlateTypes;
    }

    private void writeCherryPickRow(PrintWriter out, LabCherryPick cherryPick) {
        out.print(cherryPick.getSourceWell().getPlateNumber());
        out.print(cherryPick.getSourceCopy().getName());
        out.print(cherryPick.getSourceWell().getWellName());
        out.print(cherryPick.getSourceCopy().findPlate(cherryPick.getSourceWell().getPlateNumber()).getPlateType());
        out.print(cherryPick.getAssayPlateWellName());
        out.print(cherryPick.getAssayPlate().getAssayPlateType());
        out.print(cherryPick.getCherryPickRequest().getRequestedBy().getFullNameFirstLast());
        out.print(cherryPick.getCherryPickRequest().getScreen().getFacilityId());
        out.print(cherryPick.getCherryPickRequest().getTransferVolumePerWellApproved()
                .convert(VolumeUnit.MICROLITERS).getValue().setScale(2, RoundingMode.HALF_UP));
        out.println();
    }

    private void writeHeadersRow(PrintWriter out) {
        for (String string : CSV_FILE_HEADERS) {
            out.print(string);
        }
        out.println();
    }
}