extensions/net.sf.basedb.reggie/trunk/src/net/sf/basedb/reggie/plugins/AliquotImporter.java

Code
Comments
Other
Rev Date Author Line
5397 03 May 19 nicklas 1 package net.sf.basedb.reggie.plugins;
5397 03 May 19 nicklas 2
5397 03 May 19 nicklas 3 import java.io.IOException;
5397 03 May 19 nicklas 4 import java.io.InputStream;
5397 03 May 19 nicklas 5 import java.util.ArrayList;
5397 03 May 19 nicklas 6 import java.util.HashMap;
5397 03 May 19 nicklas 7 import java.util.List;
5397 03 May 19 nicklas 8 import java.util.Map;
5397 03 May 19 nicklas 9 import java.util.TreeMap;
5397 03 May 19 nicklas 10 import java.util.regex.Pattern;
5397 03 May 19 nicklas 11
5397 03 May 19 nicklas 12 import org.json.simple.JSONArray;
5397 03 May 19 nicklas 13 import org.json.simple.JSONObject;
5397 03 May 19 nicklas 14
5399 06 May 19 nicklas 15 import net.sf.basedb.core.BioPlate;
5397 03 May 19 nicklas 16 import net.sf.basedb.core.DbControl;
5397 03 May 19 nicklas 17 import net.sf.basedb.core.Extract;
5397 03 May 19 nicklas 18 import net.sf.basedb.core.ItemQuery;
5400 06 May 19 nicklas 19 import net.sf.basedb.core.ItemSubtype;
5461 03 Jun 19 nicklas 20 import net.sf.basedb.core.Type;
5397 03 May 19 nicklas 21 import net.sf.basedb.core.query.Expressions;
5397 03 May 19 nicklas 22 import net.sf.basedb.core.query.Hql;
5397 03 May 19 nicklas 23 import net.sf.basedb.core.query.Restrictions;
5397 03 May 19 nicklas 24 import net.sf.basedb.reggie.Reggie;
5760 26 Nov 19 nicklas 25 import net.sf.basedb.reggie.converter.ValueConverter;
5401 07 May 19 nicklas 26 import net.sf.basedb.reggie.dao.Annotationtype;
5400 06 May 19 nicklas 27 import net.sf.basedb.reggie.dao.BioplateType;
5400 06 May 19 nicklas 28 import net.sf.basedb.reggie.dao.Subtype;
5399 06 May 19 nicklas 29 import net.sf.basedb.util.Coordinate;
5399 06 May 19 nicklas 30 import net.sf.basedb.util.Values;
5420 13 May 19 nicklas 31 import net.sf.basedb.util.excel.XlsxToCsvUtil;
5397 03 May 19 nicklas 32 import net.sf.basedb.util.parser.FlatFileParser;
5397 03 May 19 nicklas 33 import net.sf.basedb.util.parser.Mapper;
5397 03 May 19 nicklas 34 import net.sf.basedb.util.parser.FlatFileParser.LineType;
5397 03 May 19 nicklas 35
5397 03 May 19 nicklas 36 public class AliquotImporter 
5397 03 May 19 nicklas 37 {
5397 03 May 19 nicklas 38   private final List<String> errorMessages;
5397 03 May 19 nicklas 39   private final List<String> warningMessages;
5397 03 May 19 nicklas 40
5444 23 May 19 nicklas 41   private final List<Aliquot> aliquots;
5399 06 May 19 nicklas 42   private final Map<String, Aliquot> aliquotsByName;
5399 06 May 19 nicklas 43   private final Map<String, Aliquot> aliquotsByLocation;
5399 06 May 19 nicklas 44   private final Map<String, AliquotPlate> platesByName;
5397 03 May 19 nicklas 45
5400 06 May 19 nicklas 46   private int numImportedPlates;
5400 06 May 19 nicklas 47   private int numImportedAliquots;
5400 06 May 19 nicklas 48   
5397 03 May 19 nicklas 49   private String namePrefix;
5420 13 May 19 nicklas 50   private List<String> worksheets;
5420 13 May 19 nicklas 51   private String parsedWorksheet;
5397 03 May 19 nicklas 52   
5397 03 May 19 nicklas 53   public AliquotImporter()
5397 03 May 19 nicklas 54   {
5397 03 May 19 nicklas 55     this.errorMessages = new ArrayList<String>();
5397 03 May 19 nicklas 56     this.warningMessages = new ArrayList<String>();
5444 23 May 19 nicklas 57     this.aliquots = new ArrayList<>();
5399 06 May 19 nicklas 58     this.aliquotsByName = new HashMap<>();
5399 06 May 19 nicklas 59     this.aliquotsByLocation = new HashMap<>();
5399 06 May 19 nicklas 60     this.platesByName = new TreeMap<>();
5397 03 May 19 nicklas 61   }
5397 03 May 19 nicklas 62   
5397 03 May 19 nicklas 63   public void setNamePrefix(String prefix)
5397 03 May 19 nicklas 64   {
5397 03 May 19 nicklas 65     this.namePrefix = prefix;
5397 03 May 19 nicklas 66   }
5397 03 May 19 nicklas 67   
5397 03 May 19 nicklas 68   public boolean hasWarning()
5397 03 May 19 nicklas 69   {
5397 03 May 19 nicklas 70     return warningMessages.size() > 0;
5397 03 May 19 nicklas 71   }
5397 03 May 19 nicklas 72   
5397 03 May 19 nicklas 73   public List<String> getWarningMessages()
5397 03 May 19 nicklas 74   {
5397 03 May 19 nicklas 75     return warningMessages;
5397 03 May 19 nicklas 76   }
5397 03 May 19 nicklas 77   
5397 03 May 19 nicklas 78   public boolean hasError()
5397 03 May 19 nicklas 79   {
5397 03 May 19 nicklas 80     return errorMessages.size() > 0;
5397 03 May 19 nicklas 81   }
5397 03 May 19 nicklas 82   
5397 03 May 19 nicklas 83   public List<String> getErrorMessages()
5397 03 May 19 nicklas 84   {
5397 03 May 19 nicklas 85     return errorMessages;
5397 03 May 19 nicklas 86   }
5397 03 May 19 nicklas 87     
5397 03 May 19 nicklas 88   public void addWarningMessage(String msg)
5397 03 May 19 nicklas 89   {
5397 03 May 19 nicklas 90     this.warningMessages.add("[Warning] " + msg);
5397 03 May 19 nicklas 91   }
5397 03 May 19 nicklas 92   
5397 03 May 19 nicklas 93   public void addErrorMessage(String msg)
5397 03 May 19 nicklas 94   {
5397 03 May 19 nicklas 95     this.errorMessages.add("[Error] " + msg);
5397 03 May 19 nicklas 96   }
5397 03 May 19 nicklas 97   
5399 06 May 19 nicklas 98   /**
5399 06 May 19 nicklas 99     Get all plates ordered by name.
5399 06 May 19 nicklas 100   */
5397 03 May 19 nicklas 101   public List<AliquotPlate> getPlates()
5397 03 May 19 nicklas 102   {
5399 06 May 19 nicklas 103     return new ArrayList<>(platesByName.values());
5397 03 May 19 nicklas 104   }
5397 03 May 19 nicklas 105   
5400 06 May 19 nicklas 106   public int getNumImportedPlates()
5400 06 May 19 nicklas 107   {
5400 06 May 19 nicklas 108     return numImportedPlates;
5400 06 May 19 nicklas 109   }
5400 06 May 19 nicklas 110   
5400 06 May 19 nicklas 111   public int getNumImportedAliquots()
5400 06 May 19 nicklas 112   {
5400 06 May 19 nicklas 113     return numImportedAliquots;
5400 06 May 19 nicklas 114   }
5400 06 May 19 nicklas 115   
5403 08 May 19 nicklas 116   public int getNumAliquots()
5403 08 May 19 nicklas 117   {
5495 14 Jun 19 nicklas 118     return aliquots.size();
5403 08 May 19 nicklas 119   }
5403 08 May 19 nicklas 120   
5420 13 May 19 nicklas 121   public List<String> getWorksheets()
5420 13 May 19 nicklas 122   {
5420 13 May 19 nicklas 123     return worksheets;
5420 13 May 19 nicklas 124   }
5420 13 May 19 nicklas 125   
5420 13 May 19 nicklas 126   public String getParsedWorksheet()
5420 13 May 19 nicklas 127   {
5420 13 May 19 nicklas 128     return parsedWorksheet;
5420 13 May 19 nicklas 129   }
5420 13 May 19 nicklas 130   
5397 03 May 19 nicklas 131   /**
5397 03 May 19 nicklas 132     Import data from the input stream.
5397 03 May 19 nicklas 133     @param in
5397 03 May 19 nicklas 134   */
5420 13 May 19 nicklas 135   public boolean doImport(DbControl dc, InputStream in, String workSheet, boolean validateOnly, String fileName)
5397 03 May 19 nicklas 136     throws IOException
5397 03 May 19 nicklas 137   {
5444 23 May 19 nicklas 138     aliquots.clear();
5399 06 May 19 nicklas 139     aliquotsByName.clear();
5399 06 May 19 nicklas 140     aliquotsByLocation.clear();
5399 06 May 19 nicklas 141     platesByName.clear();
5399 06 May 19 nicklas 142     errorMessages.clear();
5399 06 May 19 nicklas 143     warningMessages.clear();
5420 13 May 19 nicklas 144     worksheets = null;
5420 13 May 19 nicklas 145     parsedWorksheet = null;
5399 06 May 19 nicklas 146     
5400 06 May 19 nicklas 147     ItemSubtype preNormalizedDNA = Subtype.DNA_NORMALIZED_ALIQUOT.get(dc);
5400 06 May 19 nicklas 148     
5397 03 May 19 nicklas 149     FlatFileParser ffp = new FlatFileParser();
5420 13 May 19 nicklas 150     ffp.setExcelSheet(workSheet);
5397 03 May 19 nicklas 151     ffp.setDataHeaderRegexp(Pattern.compile(".*\\bName\\b.*"));
5397 03 May 19 nicklas 152     ffp.setDataSplitterRegexp(Pattern.compile("\t")); // Split on tab
5399 06 May 19 nicklas 153     ffp.setIgnoreRegexp(Pattern.compile("\\s*")); // Ignore lines with only white-space
5399 06 May 19 nicklas 154     ffp.setUseNullIfEmpty(true);
5464 04 Jun 19 nicklas 155     ffp.setTrimWhiteSpace(true);
5397 03 May 19 nicklas 156     
5397 03 May 19 nicklas 157     ffp.setInputStream(in, "UTF-8");
5420 13 May 19 nicklas 158     
5420 13 May 19 nicklas 159     XlsxToCsvUtil workbook = ffp.getCurrentExcelWorkbook();
5420 13 May 19 nicklas 160     if (workbook != null)
5420 13 May 19 nicklas 161     {
5420 13 May 19 nicklas 162       worksheets = workbook.getSheetNames();
5420 13 May 19 nicklas 163       parsedWorksheet = ffp.getExcelSheet();
5420 13 May 19 nicklas 164       fileName += "/" + parsedWorksheet;
5420 13 May 19 nicklas 165     }
5420 13 May 19 nicklas 166     
5397 03 May 19 nicklas 167     LineType headerLine = ffp.parseHeaders();
5397 03 May 19 nicklas 168     int lineNo = ffp.getParsedLines();
5397 03 May 19 nicklas 169     if (headerLine != LineType.DATA_HEADER)
5397 03 May 19 nicklas 170     {
5447 24 May 19 nicklas 171       addErrorMessage("File '" + fileName + "' line 1−" + lineNo + ": Could not find header line with 'Name' column.");
5397 03 May 19 nicklas 172       return false; // Can't continue if no data is found in the file
5397 03 May 19 nicklas 173     }
5397 03 May 19 nicklas 174     ffp.setIgnoreNonExistingColumns(true);
5397 03 May 19 nicklas 175     
5447 24 May 19 nicklas 176     String fileAndLine = "File '" + fileName + "' line " + lineNo + ": ";
5397 03 May 19 nicklas 177     Mapper nameMapper = getRequiredMapper(ffp, "Name", fileAndLine);
5397 03 May 19 nicklas 178     Mapper plateMapper = getRequiredMapper(ffp, "Plate", fileAndLine);
5397 03 May 19 nicklas 179     Mapper rowMapper = getRequiredMapper(ffp, "Row", fileAndLine);
5397 03 May 19 nicklas 180     Mapper columnMapper = getRequiredMapper(ffp, "Column", fileAndLine);
5600 13 Sep 19 nicklas 181     Mapper panelMapper = getRequiredMapper(ffp, "MIPS_Panel", fileAndLine);
5447 24 May 19 nicklas 182     Mapper poolVolumeMapper = getRequiredMapper(ffp, "PoolVolume", fileAndLine);
5397 03 May 19 nicklas 183     if (hasError()) return false;
5397 03 May 19 nicklas 184     
5401 07 May 19 nicklas 185     Mapper commentMapper = getOptionalMapper(ffp, "Comment", null);
5447 24 May 19 nicklas 186     Mapper normalTumorMapper = getOptionalMapper(ffp, "NormalTumor", fileAndLine);
5447 24 May 19 nicklas 187     Mapper formMapper = getOptionalMapper(ffp, "Form", fileAndLine);
5447 24 May 19 nicklas 188     Mapper variantMapper = getOptionalMapper(ffp, "Variant", fileAndLine);
5447 24 May 19 nicklas 189     Mapper variantListMapper = getOptionalMapper(ffp, "VariantList", fileAndLine);
5447 24 May 19 nicklas 190     Mapper screeningMapper = getOptionalMapper(ffp, "Screening", fileAndLine);
5663 11 Oct 19 nicklas 191     Mapper patientIdMapper = getOptionalMapper(ffp, "PatientID", fileAndLine);
5663 11 Oct 19 nicklas 192     Mapper provnummerMapper = getOptionalMapper(ffp, "Provnummer", fileAndLine);
5402 07 May 19 nicklas 193     
5760 26 Nov 19 nicklas 194     ValueConverter<String, String> normalTumorConverter = new TranslationConverter("TUMOR", "T", "NA", null);
5760 26 Nov 19 nicklas 195     ValueConverter<String, String> variantConverter = new TranslationConverter("0", null);
5760 26 Nov 19 nicklas 196     
5397 03 May 19 nicklas 197     while (ffp.hasMoreData())
5397 03 May 19 nicklas 198     {
5397 03 May 19 nicklas 199       FlatFileParser.Data data = ffp.nextData();
5397 03 May 19 nicklas 200       lineNo = ffp.getParsedLines();
5397 03 May 19 nicklas 201       fileAndLine = "File '" + fileName + "' line " + lineNo + ": ";
5399 06 May 19 nicklas 202
5444 23 May 19 nicklas 203       String name = nameMapper.getString(data);
5444 23 May 19 nicklas 204       if (name == null) continue; // Skip lines with no name
5444 23 May 19 nicklas 205       
5444 23 May 19 nicklas 206       Aliquot a = new Aliquot(fileAndLine);
5444 23 May 19 nicklas 207       aliquots.add(a);
5399 06 May 19 nicklas 208       a.lineNo = lineNo;
5444 23 May 19 nicklas 209       a.name = mergePrefix(namePrefix, name);
5399 06 May 19 nicklas 210       a.prefix = namePrefix;
5397 03 May 19 nicklas 211       a.plateName = plateMapper.getString(data);
5397 03 May 19 nicklas 212       a.row = rowMapper.getString(data);
5397 03 May 19 nicklas 213       a.column = columnMapper.getString(data);
5600 13 Sep 19 nicklas 214       a.panel = panelMapper.getString(data);
5447 24 May 19 nicklas 215       a.poolVolume = poolVolumeMapper.getString(data);
5401 07 May 19 nicklas 216       if (commentMapper != null) a.comment = commentMapper.getString(data);
5760 26 Nov 19 nicklas 217       if (normalTumorMapper != null) a.normalTumor = normalTumorConverter.convert(normalTumorMapper.getString(data));
5447 24 May 19 nicklas 218       if (formMapper != null) a.form = formMapper.getString(data);
5760 26 Nov 19 nicklas 219       if (variantMapper != null) a.variant = variantConverter.convert(variantMapper.getString(data));
5447 24 May 19 nicklas 220       if (variantListMapper != null) a.variantList = variantListMapper.getString(data);
5447 24 May 19 nicklas 221       if (screeningMapper != null) a.screening = screeningMapper.getString(data);
5663 11 Oct 19 nicklas 222       if (patientIdMapper != null) a.patientId = patientIdMapper.getString(data);
5663 11 Oct 19 nicklas 223       if (provnummerMapper != null) a.provnummer = provnummerMapper.getString(data);
5401 07 May 19 nicklas 224       
5444 23 May 19 nicklas 225       boolean valid = checkAliquot(dc, a);
5399 06 May 19 nicklas 226
5600 13 Sep 19 nicklas 227       if (a.panel == null)
5401 07 May 19 nicklas 228       {
5600 13 Sep 19 nicklas 229         if (a.warning == null) a.warning = "Missing MIPS_Panel";
5600 13 Sep 19 nicklas 230         addWarningMessage(fileAndLine+"Missing MIPS_Panel for '"+a.name+"'");
5401 07 May 19 nicklas 231       }
5444 23 May 19 nicklas 232       
5447 24 May 19 nicklas 233       // We should have a value in at least one of those 3 columns
5447 24 May 19 nicklas 234       if (variantMapper != null || variantListMapper != null || screeningMapper != null)
5402 07 May 19 nicklas 235       {
5447 24 May 19 nicklas 236         if (a.variant == null && a.variantList == null && a.screening == null)
5402 07 May 19 nicklas 237         {
5447 24 May 19 nicklas 238           if (a.warning == null) a.warning = "Missing data";
5447 24 May 19 nicklas 239           addWarningMessage(fileAndLine+"No variant or screening information for '"+a.name+"'");
5402 07 May 19 nicklas 240         }
5402 07 May 19 nicklas 241       }
5401 07 May 19 nicklas 242       
5400 06 May 19 nicklas 243       AliquotPlate plate = null;
5399 06 May 19 nicklas 244       if (a.plateName != null)
5397 03 May 19 nicklas 245       {
5400 06 May 19 nicklas 246         plate = platesByName.get(a.plateName);
5399 06 May 19 nicklas 247         if (plate == null)
5399 06 May 19 nicklas 248         {
5399 06 May 19 nicklas 249           plate = new AliquotPlate();
5399 06 May 19 nicklas 250           plate.name = a.plateName;
5399 06 May 19 nicklas 251           platesByName.put(plate.name, plate);
5400 06 May 19 nicklas 252           boolean plateValid = checkPlate(dc, plate, fileAndLine);
5400 06 May 19 nicklas 253           
5400 06 May 19 nicklas 254           if (!validateOnly && plateValid)
5400 06 May 19 nicklas 255           {
5400 06 May 19 nicklas 256             plate.aliquotPlate = BioPlate.getNew(dc, BioplateType.PRE_NORMALIZED_DNA.getPlateGeometry(dc), BioplateType.PRE_NORMALIZED_DNA.load(dc));
5400 06 May 19 nicklas 257             plate.aliquotPlate.setName(plate.name);
5408 08 May 19 nicklas 258             Annotationtype.PLATE_PROCESSING.setAnnotationValue(dc, plate.aliquotPlate, "DesignMIPs");
5400 06 May 19 nicklas 259             dc.saveItem(plate.aliquotPlate);
5400 06 May 19 nicklas 260             numImportedPlates++;
5400 06 May 19 nicklas 261           }
5399 06 May 19 nicklas 262         }
5444 23 May 19 nicklas 263         plate.addAliquot(a);
5397 03 May 19 nicklas 264       }
5400 06 May 19 nicklas 265       
5400 06 May 19 nicklas 266       if (!validateOnly && valid && plate.aliquotPlate != null)
5400 06 May 19 nicklas 267       {
5400 06 May 19 nicklas 268         Extract aliquot = Extract.getNew(dc);
5400 06 May 19 nicklas 269         aliquot.setName(a.name);
5400 06 May 19 nicklas 270         aliquot.setItemSubtype(preNormalizedDNA);
5400 06 May 19 nicklas 271         aliquot.setDescription(a.comment);
5400 06 May 19 nicklas 272         aliquot.getCreationEvent().setProtocol(null); // Otherwise, BASE will auto-set it to the project default
5400 06 May 19 nicklas 273         int row = Coordinate.alphaToNumeric(a.row)-1;
5400 06 May 19 nicklas 274         int column = Values.getInt(a.column)-1;
5400 06 May 19 nicklas 275         aliquot.setBioWell(plate.aliquotPlate.getBioWell(row, column));
5601 13 Sep 19 nicklas 276         if (a.panel != null) Annotationtype.MIPS_PANEL.setAnnotationValue(dc, aliquot, a.panel);
5461 03 Jun 19 nicklas 277         if (a.poolVolume != null) Annotationtype.MIPS_POOL_VOLUME.setAnnotationValue(dc, aliquot, poolVolumeMapper.getFloat(data));
5447 24 May 19 nicklas 278         if (a.normalTumor != null) Annotationtype.MIPS_NORMAL_TUMOR.setAnnotationValue(dc, aliquot, a.normalTumor);
5447 24 May 19 nicklas 279         if (a.form != null) Annotationtype.MIPS_FORM.setAnnotationValue(dc, aliquot, a.form);
5447 24 May 19 nicklas 280         if (a.variant != null) Annotationtype.MIPS_VARIANT.setAnnotationValue(dc, aliquot, a.variant);
5447 24 May 19 nicklas 281         if (a.variantList != null) Annotationtype.MIPS_VARIANT_LIST.setAnnotationValue(dc, aliquot, a.variantList);
5447 24 May 19 nicklas 282         if (a.screening != null) Annotationtype.MIPS_SCREENING.setAnnotationValue(dc, aliquot, a.screening);
5663 11 Oct 19 nicklas 283         if (a.patientId != null) Annotationtype.BRCA_PATIENTID.setAnnotationValue(dc, aliquot, a.patientId);
5663 11 Oct 19 nicklas 284         if (a.provnummer != null) Annotationtype.BRCA_PROVNUMMER.setAnnotationValue(dc, aliquot, a.provnummer);
5400 06 May 19 nicklas 285         dc.saveItem(aliquot);
5400 06 May 19 nicklas 286         numImportedAliquots++;
5400 06 May 19 nicklas 287       }
5397 03 May 19 nicklas 288     }
5397 03 May 19 nicklas 289     
5600 13 Sep 19 nicklas 290     // Finally check if there are any aliquots that have a different PoolVolume
5444 23 May 19 nicklas 291     // than the rest of the aliquots in a column
5444 23 May 19 nicklas 292     for (Aliquot a : aliquots)
5444 23 May 19 nicklas 293     {
5447 24 May 19 nicklas 294       if (a.poolVolume != null && a.plateColumn != null && a.warning == null)
5447 24 May 19 nicklas 295       {
5447 24 May 19 nicklas 296         if (!a.poolVolume.equals(a.plateColumn.volumeCounter.maxKey))
5447 24 May 19 nicklas 297         {
5461 03 Jun 19 nicklas 298           a.warning = "PoolVolume!";
5461 03 Jun 19 nicklas 299           addWarningMessage(a.fileAndLine+"PoolVolume for '"+a.name+"' is not " + a.plateColumn.volumeCounter.maxKey + "µl");
5447 24 May 19 nicklas 300         }
5447 24 May 19 nicklas 301       }
5444 23 May 19 nicklas 302     }
5444 23 May 19 nicklas 303     
5495 14 Jun 19 nicklas 304     if (aliquots.isEmpty()) 
5495 14 Jun 19 nicklas 305     {
5495 14 Jun 19 nicklas 306       addErrorMessage("File '" + fileName + "' line 1−" + lineNo + ": No aliquots.");
5495 14 Jun 19 nicklas 307     }
5495 14 Jun 19 nicklas 308     
5397 03 May 19 nicklas 309     return !hasError();
5397 03 May 19 nicklas 310   }
5397 03 May 19 nicklas 311   
5399 06 May 19 nicklas 312   /**
5399 06 May 19 nicklas 313     Get a mapper for a required column. If the column doesn't exists
5401 07 May 19 nicklas 314     an error message is logged and null is returned.
5399 06 May 19 nicklas 315   */
5397 03 May 19 nicklas 316   private Mapper getRequiredMapper(FlatFileParser ffp, String col, String fileAndLine)
5397 03 May 19 nicklas 317   {
5397 03 May 19 nicklas 318     Mapper mapper = null;
5397 03 May 19 nicklas 319     if (ffp.getColumnHeaderIndex(col) == null)
5397 03 May 19 nicklas 320     {
5397 03 May 19 nicklas 321       addErrorMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5397 03 May 19 nicklas 322     }
5397 03 May 19 nicklas 323     else
5397 03 May 19 nicklas 324     {
5397 03 May 19 nicklas 325       mapper = ffp.getMapper("\\" + col + "\\");
5397 03 May 19 nicklas 326     }
5397 03 May 19 nicklas 327     return mapper;
5397 03 May 19 nicklas 328   }
5397 03 May 19 nicklas 329   
5399 06 May 19 nicklas 330   /**
5399 06 May 19 nicklas 331     Get a mapper for an optional column. If the column doesn't exists
5401 07 May 19 nicklas 332     a warning message is logged (unless 'fileAndLine' is null) 
5401 07 May 19 nicklas 333     and null is returned.
5399 06 May 19 nicklas 334   */
5401 07 May 19 nicklas 335   private Mapper getOptionalMapper(FlatFileParser ffp, String col, String fileAndLine)
5397 03 May 19 nicklas 336   {
5401 07 May 19 nicklas 337     Mapper mapper = null;
5401 07 May 19 nicklas 338     if (ffp.getColumnHeaderIndex(col) == null)
5401 07 May 19 nicklas 339     {
5401 07 May 19 nicklas 340       if (fileAndLine != null)
5401 07 May 19 nicklas 341       {
5401 07 May 19 nicklas 342         addWarningMessage(fileAndLine+"Column '" + col + "' not found in column headers.");
5401 07 May 19 nicklas 343       }
5401 07 May 19 nicklas 344     }
5401 07 May 19 nicklas 345     else
5401 07 May 19 nicklas 346     {
5401 07 May 19 nicklas 347       mapper = ffp.getMapper("\\" + col + "\\");
5401 07 May 19 nicklas 348     }
5401 07 May 19 nicklas 349     return mapper;
5397 03 May 19 nicklas 350   }
5397 03 May 19 nicklas 351   
5399 06 May 19 nicklas 352   /**
5399 06 May 19 nicklas 353     Merge the prefix and the name. If name is null, null is returned.
5399 06 May 19 nicklas 354     If the name already starts with the prefix it is returned as it is.
5399 06 May 19 nicklas 355     Otherwise the prefix is added to the name.
5399 06 May 19 nicklas 356   */
5399 06 May 19 nicklas 357   private String mergePrefix(String prefix, String name)
5397 03 May 19 nicklas 358   {
5399 06 May 19 nicklas 359     if (name == null || name.startsWith(prefix)) return name;
5399 06 May 19 nicklas 360     return prefix+name;
5399 06 May 19 nicklas 361   }
5399 06 May 19 nicklas 362   
5399 06 May 19 nicklas 363   /**
5399 06 May 19 nicklas 364     Check that the aliquot information is good.
5399 06 May 19 nicklas 365     * A name is required
5399 06 May 19 nicklas 366     * The name must be unique within the file
5399 06 May 19 nicklas 367     * An aliquot with the same name must not exist in the database
5399 06 May 19 nicklas 368     * A plate name is required
5399 06 May 19 nicklas 369     * The location on the plate must be within A1 to H12
5399 06 May 19 nicklas 370     * The same location must not be used by more than one aliquot
5399 06 May 19 nicklas 371   */
5444 23 May 19 nicklas 372   public boolean checkAliquot(DbControl dc, Aliquot a)
5399 06 May 19 nicklas 373   {
5399 06 May 19 nicklas 374     if (a.name == null)
5397 03 May 19 nicklas 375     {
5447 24 May 19 nicklas 376       a.error = "Missing name!";
5444 23 May 19 nicklas 377       addErrorMessage(a.fileAndLine+"Missing aliquot name");
5397 03 May 19 nicklas 378       return false;
5397 03 May 19 nicklas 379     }
5399 06 May 19 nicklas 380
5399 06 May 19 nicklas 381     Aliquot other = aliquotsByName.get(a.name);
5399 06 May 19 nicklas 382     if (other != null)
5397 03 May 19 nicklas 383     {
5447 24 May 19 nicklas 384       a.error = "Duplicate name!";
5399 06 May 19 nicklas 385       if (other.error == null) other.error = "Duplicate name";
5444 23 May 19 nicklas 386       addErrorMessage(a.fileAndLine+"Duplicate name: " + a.name + " (also found on line " + other.lineNo + ")");
5397 03 May 19 nicklas 387       return false;
5397 03 May 19 nicklas 388     }
5399 06 May 19 nicklas 389     aliquotsByName.put(a.name, a);
5397 03 May 19 nicklas 390     
5397 03 May 19 nicklas 391     ItemQuery<Extract> query = Extract.getQuery();
5397 03 May 19 nicklas 392     query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5399 06 May 19 nicklas 393     query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(a.name)));
5397 03 May 19 nicklas 394     if (query.count(dc) > 0)
5397 03 May 19 nicklas 395     {
5447 24 May 19 nicklas 396       a.error = "Already exists!";
5444 23 May 19 nicklas 397       addErrorMessage(a.fileAndLine+"Already exists: " + a.name);
5397 03 May 19 nicklas 398       return false;
5397 03 May 19 nicklas 399     }
5399 06 May 19 nicklas 400     
5399 06 May 19 nicklas 401     if (a.plateName== null)
5399 06 May 19 nicklas 402     {
5447 24 May 19 nicklas 403       a.error = "No plate name!";
5444 23 May 19 nicklas 404       addErrorMessage(a.fileAndLine+"Missing plate name for '" + a.name + "'");
5399 06 May 19 nicklas 405       return false;
5399 06 May 19 nicklas 406     }
5399 06 May 19 nicklas 407     
5399 06 May 19 nicklas 408     String location = a.plateName+"["+Values.getString(a.row)+Values.getString(a.column)+"]";
5399 06 May 19 nicklas 409     if (a.row == null || a.column == null)
5399 06 May 19 nicklas 410     {
5447 24 May 19 nicklas 411       a.error = "No location!";
5444 23 May 19 nicklas 412       addErrorMessage(a.fileAndLine+"Missing location for '" + a.name + "': " + location);
5399 06 May 19 nicklas 413       return false;
5399 06 May 19 nicklas 414     }
5399 06 May 19 nicklas 415     try
5399 06 May 19 nicklas 416     {
5399 06 May 19 nicklas 417       int row = Coordinate.alphaToNumeric(a.row);
5399 06 May 19 nicklas 418       int col = Values.getInt(a.column);
5399 06 May 19 nicklas 419       if (row < 1 || row > 8 || col < 1 || col > 12)
5399 06 May 19 nicklas 420       {
5399 06 May 19 nicklas 421         throw new RuntimeException();
5399 06 May 19 nicklas 422       }
5399 06 May 19 nicklas 423     }
5399 06 May 19 nicklas 424     catch (RuntimeException ex)
5399 06 May 19 nicklas 425     {
5461 03 Jun 19 nicklas 426       a.error = "Location!";
5444 23 May 19 nicklas 427       addErrorMessage(a.fileAndLine+"Invalid location for '" + a.name + "': " + location);
5399 06 May 19 nicklas 428       return false;
5399 06 May 19 nicklas 429     }
5399 06 May 19 nicklas 430     
5399 06 May 19 nicklas 431     other = aliquotsByLocation.get(location);
5399 06 May 19 nicklas 432     if (other != null)
5399 06 May 19 nicklas 433     {
5447 24 May 19 nicklas 434       a.error = "Multiple aliquots!";
5399 06 May 19 nicklas 435       if (other.error == null) other.error = "Multiple aliquots";
5444 23 May 19 nicklas 436       addErrorMessage(a.fileAndLine+"Multiple aliquots on location '" + location + "': " + a.name + " and " + other.name + " (line " + other.lineNo + ")");
5399 06 May 19 nicklas 437       return false;
5399 06 May 19 nicklas 438     }
5399 06 May 19 nicklas 439     aliquotsByLocation.put(location, a);
5399 06 May 19 nicklas 440     
5461 03 Jun 19 nicklas 441     if (a.poolVolume != null)
5461 03 Jun 19 nicklas 442     {
5461 03 Jun 19 nicklas 443       try
5461 03 Jun 19 nicklas 444       {
5461 03 Jun 19 nicklas 445         Type.FLOAT.parseString(a.poolVolume);
5461 03 Jun 19 nicklas 446       }
5461 03 Jun 19 nicklas 447       catch (RuntimeException ex)
5461 03 Jun 19 nicklas 448       {
5461 03 Jun 19 nicklas 449         a.error = "PoolVolume!";
5461 03 Jun 19 nicklas 450         addErrorMessage(a.fileAndLine+"Invalid PoolVolume for '" + a.name + "': " + a.poolVolume);
5461 03 Jun 19 nicklas 451       }
5461 03 Jun 19 nicklas 452     }
5461 03 Jun 19 nicklas 453     
5447 24 May 19 nicklas 454     if (a.normalTumor != null)
5447 24 May 19 nicklas 455     {
5447 24 May 19 nicklas 456       try
5447 24 May 19 nicklas 457       {
5447 24 May 19 nicklas 458         Annotationtype.MIPS_NORMAL_TUMOR.get(dc).validateAnnotationValue(a.normalTumor);
5447 24 May 19 nicklas 459       }
5447 24 May 19 nicklas 460       catch (RuntimeException ex)
5447 24 May 19 nicklas 461       {
5461 03 Jun 19 nicklas 462         a.error = "NormalTumor!";
5447 24 May 19 nicklas 463         addErrorMessage(a.fileAndLine+"Invalid NormalTumor for '" + a.name + "': " + a.normalTumor);
5447 24 May 19 nicklas 464       }
5447 24 May 19 nicklas 465     }
5447 24 May 19 nicklas 466     
5397 03 May 19 nicklas 467     return true;
5397 03 May 19 nicklas 468   }
5397 03 May 19 nicklas 469   
5399 06 May 19 nicklas 470   /**
5399 06 May 19 nicklas 471     Check that the plate information is good.
5399 06 May 19 nicklas 472     * A plate with the same name must not exist in the database
5399 06 May 19 nicklas 473   */
5399 06 May 19 nicklas 474   public boolean checkPlate(DbControl dc, AliquotPlate plate, String fileAndLine)
5399 06 May 19 nicklas 475   {
5399 06 May 19 nicklas 476     ItemQuery<BioPlate> query = BioPlate.getQuery();
5399 06 May 19 nicklas 477     query.setIncludes(Reggie.INCLUDE_IN_CURRENT_PROJECT);
5399 06 May 19 nicklas 478     query.restrict(Restrictions.eq(Hql.property("name"), Expressions.string(plate.name)));
5399 06 May 19 nicklas 479     if (query.count(dc) > 0)
5399 06 May 19 nicklas 480     {
5399 06 May 19 nicklas 481       plate.error = "Plate already exists";
5399 06 May 19 nicklas 482       addErrorMessage(fileAndLine+"Plate already exists: " + plate.name);
5399 06 May 19 nicklas 483       return false;
5399 06 May 19 nicklas 484     }
5399 06 May 19 nicklas 485     return true;
5399 06 May 19 nicklas 486   }
5399 06 May 19 nicklas 487   
5397 03 May 19 nicklas 488   public static class AliquotPlate
5397 03 May 19 nicklas 489   {
5397 03 May 19 nicklas 490     String name;
5399 06 May 19 nicklas 491     String error;
5400 06 May 19 nicklas 492     BioPlate aliquotPlate;
5444 23 May 19 nicklas 493     private final List<Aliquot> aliquots;
5444 23 May 19 nicklas 494     private final PlateColumn[] columns;
5397 03 May 19 nicklas 495     
5397 03 May 19 nicklas 496     public AliquotPlate()
5397 03 May 19 nicklas 497     {
5397 03 May 19 nicklas 498       this.aliquots = new ArrayList<>();
5444 23 May 19 nicklas 499       this.columns = new PlateColumn[12];
5444 23 May 19 nicklas 500       for (int i = 0; i < columns.length; i++)
5444 23 May 19 nicklas 501       {
5444 23 May 19 nicklas 502         columns[i] = new PlateColumn();
5444 23 May 19 nicklas 503       }
5397 03 May 19 nicklas 504     }
5397 03 May 19 nicklas 505
5400 06 May 19 nicklas 506     public String getName()
5400 06 May 19 nicklas 507     {
5400 06 May 19 nicklas 508       return name;
5400 06 May 19 nicklas 509     }
5400 06 May 19 nicklas 510     
5400 06 May 19 nicklas 511     public int getNumAliquots()
5400 06 May 19 nicklas 512     {
5400 06 May 19 nicklas 513       return aliquots.size();
5400 06 May 19 nicklas 514     }
5400 06 May 19 nicklas 515     
5444 23 May 19 nicklas 516     public void addAliquot(Aliquot a)
5444 23 May 19 nicklas 517     {
5444 23 May 19 nicklas 518       aliquots.add(a);
5444 23 May 19 nicklas 519       int col = Values.getInt(a.column)-1;
5444 23 May 19 nicklas 520       if (col >= 0 && col < columns.length)
5444 23 May 19 nicklas 521       {
5444 23 May 19 nicklas 522         columns[col].addAliquot(a);
5444 23 May 19 nicklas 523       }
5444 23 May 19 nicklas 524     }
5444 23 May 19 nicklas 525     
5397 03 May 19 nicklas 526     public JSONObject toJSONObject()
5397 03 May 19 nicklas 527     {
5397 03 May 19 nicklas 528       JSONObject json = new JSONObject();
5397 03 May 19 nicklas 529       json.put("name", name);
5399 06 May 19 nicklas 530       json.put("error", error);
5397 03 May 19 nicklas 531       JSONArray jsonAliquots = new JSONArray();
5397 03 May 19 nicklas 532       for (Aliquot a :  aliquots)
5397 03 May 19 nicklas 533       {
5397 03 May 19 nicklas 534         jsonAliquots.add(a.toJSONObject());
5397 03 May 19 nicklas 535       }
5397 03 May 19 nicklas 536       json.put("aliquots", jsonAliquots);
5444 23 May 19 nicklas 537       JSONArray jsonColumns = new JSONArray();
5444 23 May 19 nicklas 538       for (PlateColumn pc : columns)
5444 23 May 19 nicklas 539       {
5444 23 May 19 nicklas 540         jsonColumns.add(pc.toJSONObject());
5444 23 May 19 nicklas 541       }
5444 23 May 19 nicklas 542       json.put("columns", jsonColumns);
5397 03 May 19 nicklas 543       return json;
5397 03 May 19 nicklas 544     }
5397 03 May 19 nicklas 545   }
5397 03 May 19 nicklas 546   
5444 23 May 19 nicklas 547   /**
5444 23 May 19 nicklas 548     Information about a single column on the plate. We are mostly
5600 13 Sep 19 nicklas 549     interested in which MIPS_Panel and volume that is used by the aliquots. 
5444 23 May 19 nicklas 550     Normally, all aliquots in the same column should have the same
5600 13 Sep 19 nicklas 551     pnael. If not, we want to detect that and display a warning.
5444 23 May 19 nicklas 552   */
5444 23 May 19 nicklas 553   public static class PlateColumn
5444 23 May 19 nicklas 554   {
5444 23 May 19 nicklas 555     private int numAliquots;
5444 23 May 19 nicklas 556     
5600 13 Sep 19 nicklas 557     private final ItemCounter panelCounter; // The MIPS_Panel used by most of the aliquots
5447 24 May 19 nicklas 558     private final ItemCounter volumeCounter; // The PoolVolume used by most of the aliquots
5444 23 May 19 nicklas 559     
5444 23 May 19 nicklas 560     public PlateColumn()
5444 23 May 19 nicklas 561     {
5600 13 Sep 19 nicklas 562       panelCounter = new ItemCounter();
5447 24 May 19 nicklas 563       volumeCounter = new ItemCounter();
5444 23 May 19 nicklas 564     }
5444 23 May 19 nicklas 565     
5444 23 May 19 nicklas 566     public void addAliquot(Aliquot a)
5444 23 May 19 nicklas 567     {
5444 23 May 19 nicklas 568       a.plateColumn = this;
5444 23 May 19 nicklas 569       numAliquots++;
5600 13 Sep 19 nicklas 570       panelCounter.count(a.panel);
5447 24 May 19 nicklas 571       volumeCounter.count(a.poolVolume);
5444 23 May 19 nicklas 572     }
5444 23 May 19 nicklas 573     
5444 23 May 19 nicklas 574     public JSONObject toJSONObject()
5444 23 May 19 nicklas 575     {
5444 23 May 19 nicklas 576       JSONObject json = new JSONObject();
5444 23 May 19 nicklas 577       json.put("numAliquots", numAliquots);
5600 13 Sep 19 nicklas 578       json.put("MIPS_Panel", panelCounter.maxKey);
5600 13 Sep 19 nicklas 579       json.put("hasMultipleMIPSPanels", panelCounter.counts.size() > 1);
5447 24 May 19 nicklas 580       json.put("MIPS_PoolVolume", volumeCounter.maxKey);
5447 24 May 19 nicklas 581       json.put("hasMultiplePoolVolumes", volumeCounter.counts.size() > 1);
5444 23 May 19 nicklas 582       return json;
5444 23 May 19 nicklas 583     }
5444 23 May 19 nicklas 584   }
5444 23 May 19 nicklas 585   
5397 03 May 19 nicklas 586   public static class Aliquot
5397 03 May 19 nicklas 587   {
5444 23 May 19 nicklas 588     final String fileAndLine;
5399 06 May 19 nicklas 589     int lineNo;
5397 03 May 19 nicklas 590     String name;
5399 06 May 19 nicklas 591     String prefix;
5397 03 May 19 nicklas 592     String plateName;
5397 03 May 19 nicklas 593     String row;
5397 03 May 19 nicklas 594     String column;
5397 03 May 19 nicklas 595     String comment;
5600 13 Sep 19 nicklas 596     String panel;
5447 24 May 19 nicklas 597     String poolVolume;
5447 24 May 19 nicklas 598     String normalTumor;
5447 24 May 19 nicklas 599     String form;
5447 24 May 19 nicklas 600     String variant;
5447 24 May 19 nicklas 601     String variantList;
5447 24 May 19 nicklas 602     String screening;
5663 11 Oct 19 nicklas 603     String patientId;
5663 11 Oct 19 nicklas 604     String provnummer;
5399 06 May 19 nicklas 605     String error;
5401 07 May 19 nicklas 606     String warning;
5444 23 May 19 nicklas 607     PlateColumn plateColumn;
5397 03 May 19 nicklas 608     
5444 23 May 19 nicklas 609     public Aliquot(String fileAndLine)
5444 23 May 19 nicklas 610     {
5444 23 May 19 nicklas 611       this.fileAndLine = fileAndLine;
5444 23 May 19 nicklas 612     }
5397 03 May 19 nicklas 613     
5397 03 May 19 nicklas 614     public JSONObject toJSONObject()
5397 03 May 19 nicklas 615     {
5397 03 May 19 nicklas 616       JSONObject json = new JSONObject();
5397 03 May 19 nicklas 617       json.put("name", name);
5399 06 May 19 nicklas 618       json.put("prefix", prefix);
5397 03 May 19 nicklas 619       json.put("plateName", plateName);
5397 03 May 19 nicklas 620       json.put("row", row);
5397 03 May 19 nicklas 621       json.put("column", column);
5397 03 May 19 nicklas 622       json.put("location", row+column);
5397 03 May 19 nicklas 623       json.put("comment", comment);
5600 13 Sep 19 nicklas 624       json.put("MIPS_Panel", panel);
5447 24 May 19 nicklas 625       json.put("MIPS_PoolVolume", poolVolume);
5447 24 May 19 nicklas 626       json.put("MIPS_NormalTumor", normalTumor);
5447 24 May 19 nicklas 627       json.put("MIPS_Form", form);
5447 24 May 19 nicklas 628       json.put("MIPS_Variant", variant);
5447 24 May 19 nicklas 629       json.put("MIPS_VariantList", variantList);
5447 24 May 19 nicklas 630       json.put("MIPS_Screening", screening);
5663 11 Oct 19 nicklas 631       json.put("BRCA_PatientID", patientId);
5663 11 Oct 19 nicklas 632       json.put("BRCA_Provnummer", provnummer);
5399 06 May 19 nicklas 633       json.put("error", error);
5401 07 May 19 nicklas 634       json.put("warning", warning);
5397 03 May 19 nicklas 635       return json;
5397 03 May 19 nicklas 636     }
5397 03 May 19 nicklas 637   }
5397 03 May 19 nicklas 638   
5447 24 May 19 nicklas 639   /**
5447 24 May 19 nicklas 640     Helper class for counting items with the same 'key' value.
5447 24 May 19 nicklas 641     The 'key' that has the highest count is stored in 'maxKey'
5447 24 May 19 nicklas 642     and the count for that key in 'maxCount'. All other keys
5447 24 May 19 nicklas 643     and counts are found in the 'counts' map.
5447 24 May 19 nicklas 644   */
5447 24 May 19 nicklas 645   public static class ItemCounter
5447 24 May 19 nicklas 646   {
5447 24 May 19 nicklas 647     String maxKey;
5447 24 May 19 nicklas 648     int maxCount;
5447 24 May 19 nicklas 649     final Map<String, Integer> counts;
5447 24 May 19 nicklas 650     
5447 24 May 19 nicklas 651     public ItemCounter() 
5447 24 May 19 nicklas 652     {
5447 24 May 19 nicklas 653       counts = new TreeMap<>();
5447 24 May 19 nicklas 654     }
5447 24 May 19 nicklas 655
5447 24 May 19 nicklas 656     void count(String key)
5447 24 May 19 nicklas 657     {
5447 24 May 19 nicklas 658       if (key == null) return;
5447 24 May 19 nicklas 659       Integer c = counts.get(key);
5447 24 May 19 nicklas 660       c = c == null ? 1 : c+1;
5447 24 May 19 nicklas 661       counts.put(key, c);
5447 24 May 19 nicklas 662       if (c > maxCount)
5447 24 May 19 nicklas 663       {
5447 24 May 19 nicklas 664         maxCount = c;
5447 24 May 19 nicklas 665         maxKey = key;
5447 24 May 19 nicklas 666       }
5447 24 May 19 nicklas 667     }
5447 24 May 19 nicklas 668     
5447 24 May 19 nicklas 669   }
5760 26 Nov 19 nicklas 670   
5760 26 Nov 19 nicklas 671   /**
5760 26 Nov 19 nicklas 672     A simple converter for string values. If a value is found in the 
5760 26 Nov 19 nicklas 673     translation map the translation is returned. Otherwise the original
5760 26 Nov 19 nicklas 674     value is returned unmodified.
5760 26 Nov 19 nicklas 675   */
5760 26 Nov 19 nicklas 676   public static class TranslationConverter
5760 26 Nov 19 nicklas 677     implements ValueConverter<String, String>
5760 26 Nov 19 nicklas 678   {
5760 26 Nov 19 nicklas 679
5760 26 Nov 19 nicklas 680     private final Map<String, String> translation;
5760 26 Nov 19 nicklas 681     
5760 26 Nov 19 nicklas 682     /**
5760 26 Nov 19 nicklas 683       Create a new translation. Key and values are provided
5760 26 Nov 19 nicklas 684       as a single array with pair-wise entries. Eg.
5760 26 Nov 19 nicklas 685       mapping[0] translates to mapping[1], mapping[2] translates to 
5760 26 Nov 19 nicklas 686       mapping[3], and so on.
5760 26 Nov 19 nicklas 687     */
5760 26 Nov 19 nicklas 688     public TranslationConverter(String... mapping) 
5760 26 Nov 19 nicklas 689     {
5760 26 Nov 19 nicklas 690       this.translation = new HashMap<>();
5760 26 Nov 19 nicklas 691       for (int i = 1; i < mapping.length; i+=2)
5760 26 Nov 19 nicklas 692       {
5760 26 Nov 19 nicklas 693         translation.put(mapping[i-1], mapping[i]);
5760 26 Nov 19 nicklas 694       }
5760 26 Nov 19 nicklas 695     }
5760 26 Nov 19 nicklas 696     
5760 26 Nov 19 nicklas 697     @Override
5760 26 Nov 19 nicklas 698     public String convert(String key) 
5760 26 Nov 19 nicklas 699     {
5760 26 Nov 19 nicklas 700       return translation.containsKey(key) ? translation.get(key) : key;
5760 26 Nov 19 nicklas 701     }
5760 26 Nov 19 nicklas 702   }
5397 03 May 19 nicklas 703 }