content: /*************************************************************************
  *                                                                        *
  *  This file is part of the 20n/act project.                             *
  *  20n/act enables DNA prediction for synthetic biology/bioengineering.  *
  *  Copyright (C) 2017 20n Labs, Inc.                                     *
  *                                                                        *
  *  Please direct all queries to act@20n.com.                             *
  *                                                                        *
  *  This program is free software: you can redistribute it and/or modify  *
  *  it under the terms of the GNU General Public License as published by  *
  *  the Free Software Foundation, either version 3 of the License, or     *
  *  (at your option) any later version.                                   *
  *                                                                        *
  *  This program is distributed in the hope that it will be useful,       *
  *  but WITHOUT ANY WARRANTY; without even the implied warranty of        *
  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
  *  GNU General Public License for more details.                          *
  *                                                                        *
  *  You should have received a copy of the GNU General Public License     *
  *  along with this program.  If not, see <http://www.gnu.org/licenses/>. *
  *                                                                        *
  *************************************************************************/
  
  package com.act.biointerpretation.l2expansion;
  
  import chemaxon.reaction.ReactionException;
  import com.act.biointerpretation.sars.NoSar;
  import com.act.biointerpretation.sars.Sar;
  import com.fasterxml.jackson.annotation.JsonIgnore;
  import com.fasterxml.jackson.databind.ObjectMapper;
  import org.apache.logging.log4j.LogManager;
  import org.apache.logging.log4j.Logger;
  
  import java.io.IOException;
  import java.io.OutputStream;
  import java.io.OutputStreamWriter;
  import java.io.Serializable;
  import java.util.Collections;
  import java.util.List;
  import java.util.Optional;
  
  public abstract class L2Expander implements Serializable {
    private static final long serialVersionUID = 5846728290095735668L;
  
    private static final Logger LOGGER = LogManager.getFormatterLogger(L2Expander.class);
  
    // This SAR accepts every substrate.
    @JsonIgnore
    protected static final List<Sar> NO_SAR = Collections.unmodifiableList(Collections.singletonList(new NoSar()));
  
    private PredictionGenerator generator;
  
    public abstract Iterable<PredictionSeed> getPredictionSeeds();
  
    public L2Expander(PredictionGenerator generator) {
      this.generator = generator;
    }
  
    /**
     * Get predictions for this expander without logging progress.
     *
     * @return A corpus of L2 predictions using the specified generator.
     */
    public L2PredictionCorpus getPredictions() {
      return getPredictions(Optional.empty());
    }
  
    /**
     * Get predictions for this expander, logging progress to the specified output stream.
     *
     * @param maybeOutputStream A stream to which to write incremental results.
     * @return A corpus of L2 predicitions using the specified generator.
     */
    public L2PredictionCorpus getPredictions(Optional<OutputStream> maybeOutputStream) {
      L2PredictionCorpus result = new L2PredictionCorpus();
  
      Optional<OutputStreamWriter> maybeWriter = maybeOutputStream.map(OutputStreamWriter::new);
  
      ObjectMapper objectMapper = new ObjectMapper();
  
      int counter = 0;
      for (PredictionSeed seed : getPredictionSeeds()) {
        if (counter % 1000 == 0) {
          LOGGER.info("Processed %d seeds", counter);
        }
        counter++;
  
        // Apply reactor to substrate if possible
        try {
          List<L2Prediction> results = generator.getPredictions(seed);
          if (maybeWriter.isPresent()) {
            try {
              /* Write results as string to ensure the object mapper doesn't close the stream we give it.  See
               * https://fasterxml.github.io/jackson-databind/javadoc/2.6/com/fasterxml/jackson/databind/ObjectMapper.html#writeValue-java.io.OutputStream-java.lang.Object-
               * for a confusing explanation of why we worry about this. */
              String resultJson = objectMapper.writeValueAsString(results);
              maybeWriter.get().write(resultJson);
              maybeWriter.get().write("\n");
              maybeWriter.get().flush(); // Flush to ensure the user can actually see the progress output.
            } catch (Exception e) {
              LOGGER.error("Caught exception when writing progress, skipping: %s", e.getMessage());
            }
          }
          result.addAll(results);
          // If there is an error on a certain RO, metabolite pair, we should log the error, but the expansion may
          // produce some valid results, so no error is thrown.
        } catch (ReactionException e) {
          LOGGER.error("ReactionException on getPredictions. %s", e.getMessage());
        } catch (IOException e) {
          LOGGER.error("IOException during prediction generation. %s", e.getMessage());
        }
      }
  
      return result;
    }
  }
  