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.annotations.MaybeNull; import com.xenoage.utils.async.AsyncProducer; import com.xenoage.utils.async.AsyncResult; import com.xenoage.utils.io.InputStream; import com.xenoage.utils.io.ZipReader; import com.xenoage.zong.io.musicxml.opus.Opus; import com.xenoage.zong.io.musicxml.opus.OpusItem; import com.xenoage.zong.io.musicxml.opus.OpusLink; /** * Resolves all {@link OpusLink} items within the given {@link Opus} to * a new {@link Opus} and returns the result. * For a compressed MusicXML file, a {@link ZipReader} has to be given, otherwise * the given base path is used. * * Reading is asynchronous, since multiple files may have to be opened * and this is only supported by non-blocking IO on all platforms. * * @author Andreas Wenger */ public class OpusLinkResolver implements AsyncProducer<Opus> { //input private Opus opus; @MaybeNull private ZipReader zip; @MaybeNull private String basePath; //working data private List<OpusItem> input; private List<OpusItem> acc; private AsyncResult<Opus> callback; public OpusLinkResolver(Opus opus, ZipReader zip, String basePath) { this.opus = opus; this.zip = zip; this.basePath = basePath; } @Override public void produce(AsyncResult<Opus> callback) { this.input = alist(opus.getItems()); this.acc = alist(); this.callback = callback; processNextItem(); } /** * Processes the next opus item in the input queue, or finishes the processing * if the queue is empty. */ private void processNextItem() { if (input.size() > 0) { //another item to resolve OpusItem inputItem = input.remove(0); if (inputItem instanceof OpusLink) { //opus link; must be resolved String filePath = ((OpusLink) inputItem).getLink().getHref(); if (zip != null) { //read zipped opus file InputStream opusStream; try { opusStream = zip.openFile(filePath); resolveItem(opusStream); } catch (com.xenoage.utils.io.FileNotFoundException ex) { callback.onFailure(ex); } } else if (basePath != null) { //read plain opus file platformUtils().openFileAsync(basePath + "/" + filePath, new AsyncResult<InputStream>() { @Override public void onSuccess(InputStream opusStream) { resolveItem(opusStream); } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } else { callback.onFailure(new IOException("neither zip nor basePath is given")); } } else if (inputItem instanceof Opus) { //opus; can contain links which must be resolved Opus childOpus = (Opus) inputItem; new OpusLinkResolver(childOpus, zip, basePath).produce(new AsyncResult<Opus>() { @Override public void onSuccess(Opus opus) { acc.add(opus); //item finished, next one processNextItem(); } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } else { //simple case. item needs not to be resolved, just add it acc.add(inputItem); processNextItem(); } } else { //all items resolved callback.onSuccess(new Opus(opus.getTitle(), acc)); } } private void resolveItem(InputStream stream) { //read opus Opus newOpus = null; try { newOpus = new OpusFileInput().readOpusFile(stream); } catch (Exception ex) { callback.onFailure(ex); return; } //this opus can again have links. resolve them recursively. new OpusLinkResolver(newOpus, zip, basePath).produce(new AsyncResult<Opus>() { @Override public void onSuccess(Opus opus) { acc.add(opus); //item finished, next one processNextItem(); } @Override public void onFailure(Exception ex) { callback.onFailure(ex); } }); } }