package com.xenoage.zong.io.musicxml.in; import static com.xenoage.utils.PlatformUtils.platformUtils; import static com.xenoage.utils.collections.CollectionUtils.alist; import java.io.IOException; import java.util.List; import com.xenoage.utils.async.AsyncResult; import com.xenoage.utils.async.AsyncProducer; import com.xenoage.utils.filter.Filter; import com.xenoage.utils.io.BufferedInputStream; import com.xenoage.utils.io.FileUtils; import com.xenoage.utils.io.InputStream; import com.xenoage.zong.core.Score; import com.xenoage.zong.io.musicxml.FileType; import com.xenoage.zong.io.musicxml.opus.Opus; /** * This class reads single or multiple {@link Score}s from * a given file, which may be an XML score, an XML opus or * a compressed MXL file. * * The result is returned asynchronously, since multiple files * may have to be opened and only non-blocking IO is supported * by all platforms. * * @author Andreas Wenger */ public class MusicXmlFileReader implements AsyncProducer<List<Score>> { //input private InputStream in; private String path; private Filter<String> scoreFileFilter; /** * Reader for a list of scores from the given file. XML scores, * XML opera and compressed MusicXML files are supported. * The given filter is used to select score files. * * The given input stream is used to read the simple MusicXML file * or the opus file. If it is a opus file, the given path must be * set, so that the linked files can be found and opened, otherwise * an empty list is returned. */ public MusicXmlFileReader(InputStream in, String path, Filter<String> scoreFileFilter) { this.in = in; this.path = path; this.scoreFileFilter = scoreFileFilter; } @Override public void produce(final AsyncResult<List<Score>> callback) { final List<Score> ret = alist(); //open stream BufferedInputStream bis = new BufferedInputStream(in); try { bis.mark(); //file type FileType fileType = FileTypeReader.getFileType(bis); bis.reset(); bis.unmark(); //open file if (fileType == FileType.XMLScorePartwise) { Score score = new MusicXmlScoreFileInput().read(bis, path); ret.add(score); callback.onSuccess(ret); } else if (fileType == FileType.XMLOpus) { //opus if (path == null) { //no path is given. we can not read the linked files. callback.onSuccess(ret); } else { //read files final String directory = FileUtils.getDirectoryName(path); OpusFileInput opusInput = new OpusFileInput(); Opus opus = opusInput.readOpusFile(bis); new OpusLinkResolver(opus, null, directory).produce(new AsyncResult<Opus>() { @Override public void onSuccess(Opus opus) { try { List<String> filePaths = scoreFileFilter.filter(opus.getScoreFilenames()); processNextScore(directory, filePaths, scoreFileFilter, ret, callback); } catch (IOException ex) { callback.onFailure(ex); } } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } } else if (fileType == FileType.Compressed) { CompressedFileInput zip = new CompressedFileInput(bis); List<String> filePaths = scoreFileFilter.filter(zip.getScoreFilenames()); for (String filePath : filePaths) { Score score = zip.loadScore(filePath); ret.add(score); } zip.close(); callback.onSuccess(ret); } else { callback.onFailure(new IOException("Unknown file type")); } } catch (IOException ex) { //try to close input stream bis.close(); //return failure callback.onFailure(ex); } } /** * Processes the next opus item in the input queue, or finishes the processing * if the queue is empty. */ private static void processNextScore(final String directory, final List<String> filePaths, final Filter<String> scoreFileFilter, final List<Score> acc, final AsyncResult<List<Score>> callback) { if (filePaths.size() > 0) { //another file to load String filePath = filePaths.remove(0); final String relativePath = directory + "/" + filePath; platformUtils().openFileAsync(relativePath, new AsyncResult<InputStream>() { @Override public void onSuccess(InputStream stream) { //input stream opened, read file new MusicXmlFileReader(stream, relativePath, scoreFileFilter) .produce(new AsyncResult<List<Score>>() { @Override public void onSuccess(List<Score> scores) { acc.addAll(scores); //async recursive call processNextScore(directory, filePaths, scoreFileFilter, acc, callback); } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } else { //all files loaded callback.onSuccess(acc); } } }