/* *****************************************************************************
* Copyright (c) 2009 Ola Spjuth.
* 2012 Jonathan Alvarsson
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Ola Spjuth - initial API and implementation
******************************************************************************/
package net.bioclipse.balloon.business;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeoutException;
import net.bioclipse.balloon.runner.BalloonRunner;
import net.bioclipse.cdk.business.ICDKManager;
import net.bioclipse.cdk.domain.ICDKMolecule;
import net.bioclipse.core.ResourcePathTransformer;
import net.bioclipse.core.business.BioclipseException;
import net.bioclipse.core.domain.IMolecule;
import net.bioclipse.core.util.TimeCalculator;
import net.bioclipse.managers.business.IBioclipseManager;
import net.bioclipse.ui.business.Activator;
import net.bioclipse.ui.business.IUIManager;
import org.apache.log4j.Logger;
import org.eclipse.core.filesystem.EFS;
import org.eclipse.core.filesystem.IFileStore;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.core.runtime.content.IContentDescription;
import org.eclipse.core.runtime.content.IContentType;
import org.openscience.cdk.io.SDFWriter;
import org.openscience.cdk.io.formats.IChemFormat;
/**
* A Bioclipse Manager for invoking Balloon
* (http://web.abo.fi/~mivainio/balloon/index.php) Fromm Balloon homepage:
* Balloon creates 3D atomic coordinates from molecular connectivity via
* distance geometry and confomer ensembles using a multi-objective genetic
* algorithm. The input can be SMILES, SDF or MOL2 format. Output is SDF or
* MOL2. Flexibility of aliphatic rings and stereochemistry about double bonds
* and tetrahedral chiral atoms is handled.
*
* @author ola
*/
public class BalloonManager implements IBioclipseManager {
private final Logger logger
= Logger.getLogger( BalloonManager.class );
private static List<String> supportedContentTypes;
static {
// These entries need to match the command in plugin.xml but found
// no easy way to read this info from there
supportedContentTypes = new ArrayList<String>();
supportedContentTypes.add( "net.bioclipse.contenttypes.smi" );
supportedContentTypes.add( "net.bioclipse.contenttypes.sdf" );
supportedContentTypes.add( "net.bioclipse.contenttypes.mdlMolFile" );
supportedContentTypes
.add( "net.bioclipse.contenttypes.cml.singleMolecule2d" );
}
/**
* Defines the Bioclipse namespace for balloon.
* Appears in the scripting language as the namespace/prefix
*/
public String getManagerName() {
return "balloon";
}
/**
* Generate 3D for a single file
*/
public String generate3Dcoordinates( String inputfile )
throws BioclipseException {
return generate3Dconformations( inputfile, 1 );
}
public List<ICDKMolecule> generateMultiple3Dcoordinates(
List<IMolecule> molecules, IProgressMonitor monitor )
throws BioclipseException {
return generateMultiple3Dconformations( molecules, 1, monitor );
}
public List<ICDKMolecule> generateMultiple3Dconformations(
List<IMolecule> molecules,
int numConf, IProgressMonitor monitor )
throws BioclipseException {
List<ICDKMolecule> retlist=new ArrayList<ICDKMolecule>();
monitor.beginTask("Balloon conformation generation", molecules.size());
int i=0;
long before = System.currentTimeMillis();
for (IMolecule mol : molecules){
i++;
List<ICDKMolecule> conformations;
try {
conformations = generate3Dconformations( mol,numConf );
//We only enforce one conformation for target conf 1
if (numConf==1)
retlist.add( conformations.get( 0 ));
else
retlist.addAll( conformations);
} catch (BioclipseException e) {
logger.error("Balloon failed on mol " + i + ". Reason: " + e.getMessage());
}
monitor.worked(1);
if (i%5==0) {
int size = molecules.size();
monitor.subTask(
"Processed: " + i + "/" + size +" molecules ("
+ TimeCalculator.generateTimeRemainEst(before, i, size)
+ ")" );
}
}
monitor.done();
return retlist;
}
/**
* Generate 3D conf for a single molecule
*/
public ICDKMolecule generate3Dcoordinates( IMolecule molecule )
throws BioclipseException {
return generate3Dconformations( molecule, 1 ).get( 0 );
}
/**
* Generate 3D for a single molecule
*/
public List<ICDKMolecule> generate3Dconformations( IMolecule molecule,
int numConf)
throws BioclipseException {
ICDKManager cdk = net.bioclipse.cdk.business.Activator
.getDefault().getJavaCDKManager();
IUIManager ui = Activator.getDefault().getUIManager();
//Copy back properties from input mol to retmol
ICDKMolecule cdkmol = cdk.asCDKMolecule(molecule);
Map<Object, Object> props = cdkmol.getAtomContainer().getProperties();
String inputfile=serializeMoleculeToTempFile(cdkmol);
String outputFile = generate3Dconformations( inputfile, numConf );
List<ICDKMolecule> retmols=null;
try {
retmols = cdk.loadMolecules( outputFile);
for (ICDKMolecule newmol : retmols){
for (Object key : props.keySet()){
Object value = props.get(key);
newmol.getAtomContainer().setProperty(key, value);
}
}
} catch ( Exception e ) {
throw new BioclipseException("Could not load output file: "
+ outputFile);
}
ui.remove( inputfile);
ui.remove( outputFile);
for (ICDKMolecule mol : retmols){
mol.setResource( null );
}
return retmols;
}
/**
* Serialize a temp molecule in Virtual and return the absolute path
* @param molecule
* @return
* @throws BioclipseException
*/
private String serializeMoleculeToTempFile( IMolecule molecule )
throws BioclipseException {
ICDKManager cdk = net.bioclipse.cdk.business.Activator
.getDefault().getJavaCDKManager();
//Write a temp molfile and return path
File tempfile=null;
try {
tempfile = File.createTempFile("balloon", ".mol");
//Write mol as MDL to the temp file
String mdlString=cdk.getMDLMolfileString( molecule );
FileWriter w = new FileWriter(tempfile);
w.write( mdlString );
w.close();
} catch ( Exception e ) {
throw new BioclipseException("Could not save temp file: "
+ tempfile + ": " + e.getMessage());
}
String tempPath=tempfile.getAbsolutePath();
if (tempPath==null)
throw new BioclipseException("Could not save temp file: "
+ tempPath);
return tempPath;
}
/**
* Generate 3D for a list of files
*/
public List<String> generate3Dcoordinates( List<String> inputfiles )
throws BioclipseException {
return generate3Dconformations( inputfiles, 1 );
}
/**
* Generate 3D for a single file with ouput file specified
*/
public String generate3Dcoordinates( String inputfile, String outputfile )
throws BioclipseException {
return generate3Dconformations( inputfile, outputfile, 1 );
}
/**
* Generate n 3D conformations for a single file
*/
public String generate3Dconformations( String inputfile,
int numConformations)
throws BioclipseException {
return generate3Dconformations( inputfile, null,numConformations );
}
/**
* Generate n 3D conformations for a list of files
*/
public List<String> generate3Dconformations( List<String> inputfiles,
int numConformations )
throws BioclipseException {
List<String> outputfiles = new ArrayList<String>();
for ( String inputfile : inputfiles ) {
String ret = generate3Dconformations( inputfile,
numConformations);
outputfiles.add( ret );
}
return outputfiles;
}
/**
* Generate a number of 3D conformations for a file with one or more
* chemical structures.
*
* @param inputfile The inputfile with the existing structures
* @param outputfile Outputfile to write, will be SDF if more than one mol
* @param numConformations Number of conformations to generate
*/
public String generate3Dconformations( String inputfile,
String outputfile,
int numConformations )
throws BioclipseException {
//Must have different input as output files
if (inputfile.equals( outputfile ))
throw new IllegalArgumentException("Outputfile must be different " +
"from inputfile for Balloon ");
IFile inIfile = ResourcePathTransformer.getInstance()
.transform( inputfile );
String infile = inIfile.getRawLocation().toOSString();
IContentDescription condesc=null;
try {
condesc = inIfile.getContentDescription();
} catch ( CoreException e ) {
throw new BioclipseException("The file " + inputfile +
" has unknown contenttype: " + e.getMessage());
}
if (condesc==null)
throw new BioclipseException("The file " + inputfile +
" has no contenttype and is hence " +
"not supported for ballloon");
//Verify content types
if (!isSupportedContenttype(condesc))
throw new BioclipseException("The file " + inputfile +
" has content type: " +
condesc.getContentType().getName() +
" which is not supported by balloon.");
IUIManager ui = Activator.getDefault().getUIManager();
String outfile="";
IContainer containerToRefresh=null;
if (outputfile==null){
outfile=constructOutputFilename( infile, numConformations );
containerToRefresh=inIfile.getParent();
}else{
IFile outIfile = ResourcePathTransformer.getInstance()
.transform( outputfile );
outfile=outIfile.getRawLocation().toOSString();
containerToRefresh=outIfile.getParent();
}
//If this is a CML file we need to serialize an MDL file as input
IContentType cmlType = Platform.getContentTypeManager()
.getContentType( "net.bioclipse.contenttypes.cml.singleMolecule2d" );
if (condesc.getContentType().isKindOf( cmlType )){
logger.debug("File is CML, serialize to temp file as MDL");
ICDKManager cdk=net.bioclipse.cdk.business.Activator.getDefault().
getJavaCDKManager();
try {
ICDKMolecule cdkmol = cdk.loadMolecule( inIfile );
File f = File.createTempFile("balloon", ".mdl");
//Write mol as MDL to the temp file
String mdlString=cdk.getMDLMolfileString( cdkmol );
FileWriter w = new FileWriter(f);
w.write( mdlString );
w.close();
logger.debug("Wrote temp MDL file as: " + f.getAbsolutePath());
//Set this file as input file
infile=f.getAbsolutePath();
} catch ( Exception e ) {
throw new BioclipseException("Could not parse input file: " +
e.getMessage());
}
}
logger.debug( "Infile transformed to: " + infile );
logger.debug( "Outfile transformed to: " + outfile );
logger.debug( "Parent folder to refresh: "
+ containerToRefresh.getName() );
try {
//Read timeout from prefs
int timeout = net.bioclipse.balloon.business.Activator
.getDefault().getPreferenceStore()
.getInt( net.bioclipse.balloon.business.Activator
.BALLOON_TIMEOUT );
//Just to be sure...
if (timeout<=0)
timeout = net.bioclipse.balloon.business
.Activator.DEFAULT_BALLOON_TIMEOUT;
//Seconds -> ms
Long msTimout=new Long(timeout*1000);
//Create a native runner and execute Balloon with it for a
//certain timeout writing from inputfile to outputfile with
//desired number of conformations
BalloonRunner runner=new BalloonRunner(msTimout);
boolean status=runner.runBalloon( infile,outfile,
numConformations );
if (!status){
throw new BioclipseException(
"Balloon execution failed. Native " +
"BalloonRunner returned false." );
}
} catch ( ExecutionException e ) {
throw new BioclipseException( "Balloon execution failed. Reason: "
+ e.getMessage() );
} catch ( InterruptedException e ) {
throw new BioclipseException( "Balloon Was interrupted. Reason: "
+ e.getMessage() );
} catch ( TimeoutException e ) {
throw new BioclipseException( "Balloon timed out. Reason: "
+ e.getMessage() );
} catch ( IOException e ) {
throw new BioclipseException( "Balloon I/O error. Reason: "
+ e.getMessage() );
}
logger.debug("Balloon run successful, wrote file: " + outfile);
//Refresh navigator and select the produced file
ui.refresh(containerToRefresh.getFullPath().toOSString());
// ui.revealAndSelect( outfile );
return outfile;
}
private boolean isSupportedContenttype(IContentDescription condesc) {
for (String supCon : supportedContentTypes){
IContentType testType = Platform.getContentTypeManager()
.getContentType( supCon );
if ( testType != null &&
condesc.getContentType().isKindOf( testType ) )
return true;
}
return false;
}
static IPath constructOutputFile(IPath inPath,int numConformations) {
String ext="";
if(numConformations >1 || inPath.getFileExtension().equals("sdf")) {
ext="sdf";
} else {
ext="mdl";
}
String name = inPath.removeFileExtension().lastSegment()+"_3d";
IPath rootPath = inPath.removeFileExtension().removeLastSegments(1);
IPath result = rootPath.append(name);
for(int cnt=1;result.addFileExtension(ext).toFile().exists();cnt++) {
result = rootPath.append(name+String.format("_%d",cnt));
}
return result.addFileExtension(ext);
}
/**
* Helper method to construct output filename from inputfilename +_3d
* @param inputfile
* @param numConformations
* @return
*/
static String constructOutputFilename( String inputfile,
int numConformations ) {
int lastpathsep = inputfile.lastIndexOf( File.separator );
String path = inputfile.substring( 0, lastpathsep );
String name = inputfile.substring( lastpathsep + 1,
inputfile.length() - 4 );
String currentExtension = inputfile.substring( inputfile.length() - 4,
inputfile.length() );
String ext = "";
if (numConformations>1) ext = ".sdf";
else if(currentExtension.equals(".sdf")) ext = ".sdf";
else ext = ".mdl";
// else ext = currentExtension;
//TODO: bring this back if we decide to convert back to CML after balloon
String pathfile = path + File.separator + name;
int cnt = 1;
String outfile = getAFilename( pathfile, ext, cnt );
File file =new File(outfile);
while (file.exists() ) {
cnt++;
outfile = getAFilename( pathfile, ext, cnt );
file = new File(outfile);
}
return outfile;
}
/**
* Increment file numbering to get unique file
* @param pathname
* @param ext
* @param cnt
* @return
*/
static String getAFilename(String pathname, String ext, int cnt) {
if (cnt<=1)
return pathname+"_3d" + ext;
else
return pathname+"_3d_" + cnt + ext;
}
/**
* Thread safe method for calling balloon with one mdl-file
*
* @param infile
* @param numConformations
* @return
* @throws BioclipseException
*/
private String calculateWithBalloon( String infile, int numConformations )
throws BioclipseException {
String outfile = constructOutputFilename( infile, numConformations );
try {
//Read timeout from prefs
int timeout = net.bioclipse.balloon.business.Activator
.getDefault().getPreferenceStore()
.getInt( net.bioclipse.balloon.business.Activator
.BALLOON_TIMEOUT );
//Just to be sure...
if (timeout<=0)
timeout = net.bioclipse.balloon.business
.Activator.DEFAULT_BALLOON_TIMEOUT;
//Seconds -> ms
Long msTimout=new Long(timeout*1000);
//Create a native runner and execute Balloon with it for a
//certain timeout writing from inputfile to outputfile with
//desired number of conformations
BalloonRunner runner=new BalloonRunner(msTimout);
boolean failed =
!runner.runBalloon( infile, outfile,
numConformations );
if ( failed ) {
throw new BioclipseException(
"Balloon execution failed. Native " +
"BalloonRunner returned false." );
}
} catch ( ExecutionException e ) {
throw new BioclipseException( "Balloon execution failed. Reason: "
+ e.getMessage(), e );
} catch ( InterruptedException e ) {
throw new BioclipseException( "Balloon Was interrupted. Reason: "
+ e.getMessage(), e );
} catch ( TimeoutException e ) {
throw new BioclipseException( "Balloon timed out. Reason: "
+ e.getMessage(), e );
} catch ( IOException e ) {
throw new BioclipseException( "Balloon I/O error. Reason: "
+ e.getMessage(), e );
}
return outfile;
}
public IFile generate3Dcoordinates( final IFile input,
final IProgressMonitor progressMonitor )
throws BioclipseException,
CoreException,
IOException {
final SubMonitor monitor = SubMonitor.convert(progressMonitor);
final ICDKManager cdk = net.bioclipse.cdk.business.Activator.getDefault().getJavaCDKManager();
monitor.beginTask( "Generating 3D coordinates", 10000 );
final int numOfMolcules = cdk.numberOfEntriesInSDF( input, monitor.newChild(10) );
monitor.setWorkRemaining(numOfMolcules *30);
final String file =
constructOutputFilename( input.getRawLocation()
.toOSString(), 1 );
final BlockingQueue<MolInfo<MolPos>> inputMoleculesQueue =
new ArrayBlockingQueue<MolInfo<MolPos>>( 10 );
final BlockingQueue<MolInfo<MolPos>> outputMoleculesQueue =
new ArrayBlockingQueue<MolInfo<MolPos>>( 10 );
final Boolean[] fileIsParsed = { Boolean.FALSE};
final MolInfo<MolPos> POISION = MolInfo.poision();
final int numThreads = Runtime.getRuntime().availableProcessors();
// @new thread
Runnable parse = new Runnable() {
public void run() {
try {
Iterator<? extends ICDKMolecule> parserIterator =
cdk.createMoleculeIterator( input );
long pos = 0;
while ( parserIterator.hasNext() ) {
++pos;
MolPos mp = null;
MolInfo<MolPos> newMol = MolInfo.nothing(pos);
try {
ICDKMolecule molecule = parserIterator.next();
// save to temp file
String tempFile = serializeMoleculeToTempFile( molecule );
mp = new MolPos( molecule .getAtomContainer()
.getProperties(),
tempFile );
if ( monitor.isCanceled() )
break;
} catch (Exception e) {
logger.error(e.getMessage(),e);
newMol = MolInfo.error(newMol, e);
}
inputMoleculesQueue.put( MolInfo.some(newMol,mp) );
}
for ( int i = 0; i < numThreads; i++ )
inputMoleculesQueue.put( POISION );
} catch ( Exception e ) {
logger.error(e.getMessage(),e);
}
fileIsParsed[0] = Boolean.TRUE;
}
};
Runnable generator = new Runnable() {
public void run() {
while ( !fileIsParsed[0] || !inputMoleculesQueue.isEmpty() ) {
try {
MolInfo<MolPos> input = inputMoleculesQueue.take();
MolInfo<MolPos> output = MolInfo.nothing(input.pos);
if(input.equals(POISION)) {
break;
}
try{
for(MolPos in:input){
String outputFile = calculateWithBalloon(in.file, 1);
MolPos out = in.newOutput(outputFile);
output = MolInfo.some(input, out);
}
} catch ( Exception e) {
for(MolPos in:input){
logger.error("File: "+in.file);
}
logger.error(e.getMessage(),e);
output = MolInfo.error(input,e);
}
outputMoleculesQueue.put( output );
if ( monitor.isCanceled() )
break;
} catch (InterruptedException e) {
logger.info("Interrupted: "+e.getMessage(),e);
}
}
try {
outputMoleculesQueue.put(POISION);
} catch (InterruptedException e) {
logger.info("Interrupted: "+e.getMessage(),e);
}
}
};
Runnable writer = new Runnable() {
int numberOfThreads = numThreads;
public void run() {
SDFWriter mdlwriter;
try {
mdlwriter = new SDFWriter(
new FileWriter( new File( file ) ) );
} catch ( IOException e) {
logger.error( e.getMessage(), e );
return;
}
long pos = 1;
long before = System.currentTimeMillis();
LinkedList<MolInfo<MolPos>> buffer = new LinkedList<MolInfo<MolPos>>();
int foundPoinsions = 0;
while ( !buffer.isEmpty() || foundPoinsions < numberOfThreads ) {
try {
MolInfo<MolPos> input = null;
if ( !buffer.isEmpty()
&& outputMoleculesQueue.isEmpty() )
input = buffer.pop();
else
input = outputMoleculesQueue.take();
if ( input == POISION ) {
foundPoinsions++;
continue;
}
// Buffer if not in order
if ( pos != input.pos ) {
// check in buffer
MolInfo<MolPos> newInput = null;
Iterator<MolInfo<MolPos>> bufferIterator = buffer.iterator();
while ( bufferIterator.hasNext() ) {
MolInfo<MolPos> p = bufferIterator.next();
if ( pos == p.pos ) {
bufferIterator.remove();
newInput = p;
break;
}
}
buffer.add( input );
if ( newInput == null ) {
continue;
} else
input = newInput;
}
++pos;
SubMonitor progress = monitor.newChild(30);
for(MolPos in:input) {
List<ICDKMolecule> molecules = Collections.emptyList();
IChemFormat format = cdk.guessFormatFromExtension(in.file);
IFileStore fileStore = EFS.getLocalFileSystem().getStore(new Path(in.file));
InputStream is = fileStore.openInputStream(EFS.NONE, progress.newChild(10));
molecules = cdk.loadMolecules(is, format, progress.newChild(10));
ICDKMolecule molecule = molecules.get(0);
molecule.getAtomContainer()
.setProperties( in.properties );
mdlwriter.write(molecule.getAtomContainer());
progress.worked(10);
}
monitor.setWorkRemaining((int) (numOfMolcules-pos)*30);
monitor.subTask( "Done " + pos + "/" + numOfMolcules
+ " (" + TimeCalculator.generateTimeRemainEst(
before, (int)pos, numOfMolcules ) + ")" );
if ( monitor.isCanceled() )
break;
} catch ( Exception e ) {
logger.error( e.getMessage(), e );
}
}
try {
mdlwriter.close();
} catch (IOException e) {
logger.error( e.getMessage(), e );
}
}
};
Thread parserThread = new Thread( parse );
parserThread.start();
Thread[] workers = new Thread[numThreads];
for ( int i = 0; i < numThreads; i++ )
(workers[i] = new Thread( generator,"Balloon Worker "+i )).start();
Thread writerThread = new Thread( writer );
writerThread.start();
while ( true ) {
try {
writerThread.join();
parserThread.join();
break;
} catch ( InterruptedException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
int runCount =0;
for(Thread t:workers){
if(t.isAlive()) {
runCount++;
t.interrupt();
}
}
logger.debug(runCount+" treads is still alive");
return ResourcePathTransformer.getInstance()
.transform( file );
}
}
abstract class MolInfo<T> implements Iterable<T>{
private MolInfo(long pos) {
this.pos = pos;
}
final long pos;
public static class Some<T> extends MolInfo<T> {
private T value;
Some(long pos,T value){
super(pos);
this.value = value;
}
public Iterator<T> iterator() {
return new ImmutableIterator<T>() {
boolean hasNext = true;
public boolean hasNext() {
return hasNext;
}
public T next() {
if( !hasNext) throw new NoSuchElementException();
hasNext = false;
return value;
}
};
}
}
Iterator<T> EMPTY = new ImmutableIterator<T>() {
public boolean hasNext() { return false;}
public T next() {
throw new NoSuchElementException();
}
};
public static class Error<T> extends MolInfo<T> {
private final Throwable e;
Error(long pos, Throwable e) {
super(pos);
this.e = e;
}
public Iterator<T> iterator() { return EMPTY;}
}
public static class Nothing<T> extends MolInfo<T> {
Nothing(long pos) {
super(pos);
}
public Iterator<T> iterator() { return EMPTY;}
}
private static class Poision<T> extends MolInfo<T> {
public Poision() {super(-1);}
public Iterator<T> iterator() {
return EMPTY;
}
}
private static Poision<Object> poision = new Poision<Object>();
private static Nothing<Object> nothing = new Nothing<Object>(-1);
private static abstract class ImmutableIterator<T>
implements Iterator<T> {
public void remove() {
throw new UnsupportedOperationException();
}
}
@SuppressWarnings("unchecked")
static <T> MolInfo<T> error(long pos,Throwable e) {
return (Error<T>) new Error<Object>(pos,e);
}
static <T> MolInfo<T> error(MolInfo<T> in,Throwable e) {
return error(in.pos,e);
}
static <T> MolInfo<T> some(MolInfo<T> in, T value) {
return value == null ? MolInfo.<T>nothing(in.pos):
new Some<T>(in.pos,value);
}
@SuppressWarnings("unchecked")
static <T> MolInfo<T> some(long pos, T value) {
return value == null ? MolInfo.<T>nothing(pos):
value instanceof MolInfo<?> ? (MolInfo<T>) value: new Some<T>(pos,value);
}
@SuppressWarnings("unchecked")
static <T> MolInfo<T> poision() {
return (Poision<T>) poision;
}
@SuppressWarnings("unchecked")
static <T> MolInfo<T> nothing(long pos) {
return (Nothing<T>) new Nothing<Object>(pos);
}
}
class MolPos{
final Map<Object, Object> properties;
final String file;
public MolPos( Map<Object, Object> properties, String file) {
this.properties = properties;
this.file = file;
}
public MolPos newOutput( String outputFile ) {
return new MolPos( properties, outputFile );
}
}