gt:             for (Device dev: monitors){
context: package ch.psi.pshell.scan;
  
  import ch.psi.pshell.core.InlineDevice;
  import ch.psi.pshell.bs.Stream;
  import ch.psi.pshell.bs.Waveform;
  import ch.psi.pshell.core.Context;
  import ch.psi.pshell.core.ExecutionParameters;
  import ch.psi.pshell.core.Nameable;
  import ch.psi.pshell.data.Layout;
  import ch.psi.pshell.data.Provider;
  import ch.psi.pshell.device.*;
  import ch.psi.pshell.device.Readable;
  import ch.psi.pshell.scripting.JepUtils;
  import ch.psi.utils.*;
  
  import java.io.IOException;
  import java.lang.reflect.Array;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.LinkedHashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.logging.Level;
  import java.util.logging.Logger;
  import jep.NDArray;
  
  /**
   *
   */
  public abstract class ScanBase extends ObservableBase<ScanListener> implements Scan {
  
      static final Logger logger = Logger.getLogger(ScanBase.class.getName());
  
      final Writable[] writables;
      final Readable[] readables;
      final double[] start;
      final double[] end;
      final double[] stepSize;
      final int[] numberOfSteps;
      final boolean relative;
      final int latency;
      final int passes;
      final boolean zigzag;
  
      ScanCallbacks callbacks;
      ScanResult result;
      int recordIndex = 0;
      int recordIndexOffset = 0;
      int passOffset = 0;
      long startTimestamp;
      long endTimestamp;
      boolean canPause=true;
      
      String plotTitle;
      boolean hidden;
      boolean splitPasses;
      
      int scanIndex = -1;
      String scanPath;
      String tag;
      int pass = 1;
      boolean keep;
      String name = "Scan";
      Layout dataLayout;
      Provider dataProvider;
      Device[] monitors;
      Readable[] diags;
      Readable[] snaps;
      Map meta;
      
      boolean useWritableReadback = getScansUseWritableReadback();
      boolean restorePosition = getRestorePositionOnRelativeScans();
      boolean abortOnReadableError = getAbortScansOnReadableError();
      boolean checkPositions = ScanBase.getScansCheckPositions();
      boolean parallelPositioning = getScansParallelPositioning();
      boolean initialMove = getScansTriggerInitialMove();
      boolean resampleOnInvalidate = true;
  
      public ScanBase(Writable[] writables, Readable[] readables, double[] start, double[] end, int numberOfSteps[],
              boolean relative, int latency, int passes, boolean zigzag) {
          this.writables = writables;
          this.readables = readables;
          this.start = start;
          this.end = end;
          this.relative = relative;
          this.latency = latency;
          this.passes = passes;
          this.zigzag = zigzag;
  
          if ((numberOfSteps.length == 1) && (writables.length > 1)) {
              int steps = numberOfSteps[0];
              numberOfSteps = new int[writables.length];
              Arrays.fill(numberOfSteps, steps);
          }
          this.numberOfSteps = numberOfSteps;
  
          stepSize = new double[start.length];
          for (int i = 0; i < start.length; i++) {
              if (this.numberOfSteps[i] == 0) {
                  stepSize[i] = 0.0;
                  end[i] = start[i];
              } else {
                  stepSize[i] = (end[i] - start[i]) / this.numberOfSteps[i];
              }
          }
          assertFieldsOk();
      }
  
      public ScanBase(Writable[] writables, Readable[] readables, double[] start, double[] end, double stepSize[],
              boolean relative, int latency, int passes, boolean zigzag) {
          this.writables = writables;
          this.readables = readables;
          this.start = start;
          this.end = end;
          this.relative = relative;
          this.latency = latency;
          this.passes = passes;
          this.zigzag = zigzag;
          this.stepSize = stepSize;
          this.numberOfSteps = new int[start.length];
          assertFieldsOk();
          for (int i = 0; i < start.length; i++) {
              if (stepSize[i] == 0) {
                  this.numberOfSteps[i] = 0;
              } else {
                  //this.numberOfSteps[i] = (int) Math.ceil((end[i] - start[i]) / stepSize[i]);            
                  //Adding the final point if steps are not integer generates problems in plotting
                  this.numberOfSteps[i] = (int) Math.floor((end[i] - start[i]) / stepSize[i]);
              }
              this.end[i] = start[i] + stepSize[i] * this.numberOfSteps[i];
          }
          //if (Context.getInstance() != null) {
          //    for (ScanListener listener : Context.getInstance().getScanListeners()) {
          //        addListener(listener);
          //    }
          //}
      }
      
      public void setCallbacks(ScanCallbacks callbacks){
          this.callbacks=callbacks;
      }
  
      public ScanCallbacks getCallbacks(){
          return callbacks;
      }
      
      @Override
      public Layout getDataLayout(){
          return dataLayout;
      }
      
      @Override
      public Provider getDataProvider(){
          return dataProvider;
      }    
      
      @Override
      public void setMeta(Map meta){
          this.meta = meta;
          if (isStarted()){
              try{
                  getDataLayout().onMeta(this, meta);
              } catch(Exception ex){
                  logger.log(Level.WARNING, null, ex);
              }
          }
      }
      
      @Override
      public Map getMeta(){
          return meta;
      }
  
      public void addMeta(Map<String, Object> meta) throws IOException{
          if (getDataLayout()!=null) {
              getDataLayout().onMeta(this, meta);
          }        
          this.meta.putAll(meta);
      }
  
      
      @Override
      public Device[] getMonitors(){        
          if (monitors==null){
              return null;
          }
          ArrayList<Device> ret = new ArrayList<>();
          
          for (Device dev : monitors){
              if (dev instanceof Otf){
                  if (dev.getComponents().length>0){
                      for (Device component: dev.getComponents()){
                          ret.add(component);
                      }
                      continue;
                  }
              } 
              ret.add(dev);      
          }        
          return ret.toArray(new Device[0]);
      }    
  
      @Override
      public void setMonitors(Device[] monitors){
          this.monitors = monitors;
      }    
  
      @Override
      public Readable[] getDiags(){        
          return diags;
      }    
  
      @Override
      public void setDiags(Readable[] diags){
          this.diags = diags;
      }    
      
      @Override
      public void setSnaps(Readable[] snaps){
          this.snaps = snaps;
      }    
      
      @Override
      public Readable[] getSnaps(){
          return snaps;
      }        
  
      int settleTimeout = getScansSettleTimeout();
  
      @Override
      public int getSettleTimeout() {
          return settleTimeout;
      }
  
      @Override
      public void setSettleTimeout(int value) {
          settleTimeout = value;
      }
  
      void assertFieldsOk() {
          if ((writables == null) || (readables == null)) {
              throw new IllegalArgumentException();
          }
  
          for (int i = 0; i < writables.length; i++) {
              if (isPositiveDirection(i) && (stepSize[i] < 0)) {
                  //throw new IllegalArgumentException();
                  stepSize[i] *= -1;
  
              } else if (isNegativeDirection(i) && (stepSize[i] > 0)) {
                  //throw new IllegalArgumentException();
                  stepSize[i] *= -1;
              }
          }
      }
  
      @Override
      public void setPlotTitle(String plotTitle) {
          this.plotTitle = plotTitle;
      }
      
      @Override
      public String getPlotTitle(){
          return plotTitle;
      }
      
      @Override
      public void setHidden(boolean value){
          hidden = value;
      }
      
      @Override
      public boolean isHidden(){
          return hidden;
      }
  
      @Override
      public void setSplitPasses(boolean value){
          splitPasses = value;
      }
      
      @Override
      public boolean getSplitPasses(){
          return splitPasses;
      }
  
      
      protected boolean isPositiveDirection(int writableIndex) {
          return (start[writableIndex] <= end[writableIndex]);
      }
  
      protected boolean isNegativeDirection(int writableIndex) {
          return (start[writableIndex] > end[writableIndex]);
      }
  
      protected void moveToStart() throws IOException, InterruptedException {
          setPosition(getStart());
      }
  
      protected void moveToEnd() throws IOException, InterruptedException {
          setPosition(getEnd());
      }
  
      protected void setPosition(double[] position) throws IOException, InterruptedException {
          if (getParallelPositioning()){
              for (int i = 0; i < getWritables().length; i++) {
                  getWritables()[i].write(position[i]);
              }
              waitSettled(position);
          } else {
               for (int i = 0; i < getWritables().length; i++) {
                  getWritables()[i].write(position[i]);
                  waitSettled(getWritables()[i], position[i], stepSize[i]);
              }     
          }
      }
  
      protected void waitSettled(double[] position) throws IOException, InterruptedException {
          if (position.length < getWritables().length) {
              throw new IllegalArgumentException();
          }
          for (int i = 0; i < getWritables().length; i++) {
              waitSettled(getWritables()[i], position[i], stepSize[i]);
          }
      }
  
      protected ReadonlyRegister getReadback(Writable writable) {
          if (writable instanceof ReadbackDevice) {
              return ((ReadbackDevice) writable).getReadback();
          } else if (writable instanceof ReadonlyRegister) {
              return (ReadonlyRegister) writable;
          } else if (writable instanceof Readable) {
              return new ReadableRegister((Readable) writable);
          }
          return null;
      }
  
      protected void waitSettled(Writable writable, double pos, double stepSize) throws IOException, InterruptedException {
          if (writable instanceof Movable) {
              Movable m = (Movable) writable;
              m.waitReady(settleTimeout);
              if (writable instanceof Motor) {
                  if (getCheckPositions()) {
                      m.assertInPosition(pos);
                  }
              } else {
                  //Filtering some possible overshooting (Motors manage internally)
                  m.waitInPosition(pos, 10000);
              }
          } else {
              ReadonlyRegister readback = getReadback(writable);
              if ((readback != null) && (writable instanceof Resolved)) {
                  double resolution = ((Resolved) writable).getResolution();
                  readback.waitValueInRange(pos, resolution, settleTimeout);
              } else if (writable instanceof Device) {
                  ((Device) writable).waitStateNot(State.Busy, -1);
              }
          }
      }
  
      volatile Thread executionThread;
      double[] initialPosition;
  
      protected void readInitialPosition() throws IOException, InterruptedException {
          initialPosition = new double[writables.length];
          for (int i = 0; i < initialPosition.length; i++) {
              try {
                  initialPosition[i] = (Double) ((ch.psi.pshell.device.Readable) writables[i]).read();
              } catch (IOException | InterruptedException ex) {
                  throw ex;
              } catch (Exception ex) {
                  throw new IOException("Positioner type must be <Double> for relative scans: " + writables[i].getAlias());
              }
          }
      }
  
      /**
       * The position of the Writables in the beginning of a relative scan
       */
      public double[] getInitialPosition() {
          return initialPosition;
      }
  
      protected double getWritablePosition(int step, int writableIndex) {
          double ret = getStart()[writableIndex] + (getStepSize()[writableIndex] * step);
          if (isPositiveDirection(writableIndex)) {
              ret = Math.min(ret, getEnd()[writableIndex]);
          } else {
              ret = Math.max(ret, getEnd()[writableIndex]);
          }
          return ret;
      }
  
      protected double[] getWritablesPositions(int step) {
          double[] pos = new double[writables.length];
          for (int i = 0; i < getWritables().length; i++) {
              pos[i] = getWritablePosition(step, i);
          }
          return pos;
      }
      
      
      DeviceListener monitorListener = new DeviceAdapter() {
          @Override
          public void onCacheChanged(Device device, Object value, Object former, long timestamp, boolean valueChange) {
              triggerMonitor(device, value, timestamp);
          }
      };
      
      
      protected void startMonitor(Device dev){
          dev.addListener(monitorListener);
      }
      
      protected void stopMonitor(Device dev){
          dev.removeListener(monitorListener);
      }
      
      protected void startMonitors(){
          if (monitors != null){
              for (Device dev: monitors){
                  try {
                      if (dev instanceof Otf){
                          logger.info("Starting OTF: " + dev.getAlias());
                          ((Otf) dev).start();
                          if (dev.getComponents().length>0){
                              for (Device component: dev.getComponents()){
                                  startMonitor(component);
                              }
                              continue;
                          }
                      }                    
                      startMonitor(dev);
                  } catch (Exception ex) {
                      logger.log(Level.WARNING, null, ex);
                  }
              }
          }        
      }
    
  
      protected void stopMonitors(){
          if (monitors != null){
              for (Device dev: monitors){
                  try {
                      if (dev instanceof Otf){
                          if (!isAborted()){
                              logger.info("Waiting OTF to complete: " + dev.getAlias());
                              dev.waitReady(-1);
                              logger.info("OTF completed: " + dev.getAlias());
                          }
                      }
                      stopMonitor(dev);
                  } catch (Exception ex) {
                      logger.log(Level.WARNING, null, ex);
                  }
              }
          }        
      }
  
      @Override
      public void start() throws IOException, InterruptedException {
          Boolean fail = false;
          initialPosition = null;
          try {
              assertFieldsOk();
              recordIndex = 0;
              result = newResult();
              try{
                  openDevices();
                  if (relative) {
                      readInitialPosition();
                  }            
                  triggerStarted();
                  if(getInitialMove()){
                      moveToStart();
                  }
                  try {
                      onBeforeScan();
                      if (getCallbacks()!=null){
                          getCallbacks().onBeforeScan(this);
                      }
                      startMonitors();
                      try {
                          for (pass = 1; pass <= getNumberOfPasses(); pass++) {
                              passOffset = recordIndex;
                              onBeforePass(pass);
                              if (getCallbacks()!=null){
                                  getCallbacks().onBeforePass(this, pass);
                              }
                              doScan();
                              onAfterPass(pass);
                              if (getCallbacks()!=null){
                                  getCallbacks().onAfterPass(this, pass);
                              }
                              if (getSplitPasses()){
                                  if(pass < getNumberOfPasses()){
                                      Context.getInstance().getDataManager().splitScanData(this);
                                      setRecordIndexOffset(getRecordIndex());
                                  }
                              }
                          }
                      } finally {
                          stopMonitors();
                          onAfterScan();
                          if (getCallbacks()!=null){
                              getCallbacks().onAfterScan(this);
                          }  
                      }
                      triggerEnded(null);
                  } catch (Exception ex) {
                      fail = true;
                      try {
                          assertNotAborted();
                      } catch (Exception e) {
                          triggerEnded(e);
                          throw e;
                      }
                      triggerEnded(ex);
                      throw ex;
                  } finally {
                      if (relative && restorePosition && (initialPosition!=null)) {
                          try {
                              setPosition(initialPosition);
                          } catch (InterruptedException ex) {
                              if (!fail) {
                                  throw ex;
                              }
                          } catch (Exception ex) {
                              logger.log(Level.WARNING, null, ex);
                          }
                      }                
                  }
              } finally{
                  closeDevices();
              }
          } finally {
              endTimestamp = System.currentTimeMillis();
          }
      }
  
      protected ScanResult newResult() {
          return new ScanResult(this);
      }
  
      boolean aborted;
  
      public boolean isAborted() {
          return aborted;
      }
  
      @Override
      final public void abort() throws InterruptedException {
          logger.info("Aborting: " + toString());
          aborted = true;
          doAbort();
      }
       
      protected void doAbort() throws InterruptedException {
          //Not stopping relative moves because it affects recovering position
          if (!relative || !restorePosition) {
              stopAll();
          }
      }
  
      protected void assertNotAborted() throws ScanAbortedException {
          if (isAborted()) {
              throw new ScanAbortedException();
          }
      }
  
      boolean paused;
  
      @Override
      public boolean isPaused() {
          return paused;
      }
  
      @Override
      final public void pause() throws InterruptedException {
          if (!isPaused() && !isAborted()){
              logger.info("Pausing: " + toString());
              paused = true;
              doPause();
          }
      }  
      
      @Override
      final public void resume() throws InterruptedException {
          if (isPaused() && !isAborted()){
              logger.info("Resuming: " + toString());
              paused = false;
              doResume();
          }
      }      
      
      protected void doPause() throws InterruptedException {
      }       
      
      protected void doResume() throws InterruptedException {
      }    
      
      @Override
      public boolean canPause(){
          return canPause;
      }
      
      @Override
      public void setCanPause(boolean value){
          canPause = value;
      }
      
      protected void waitPauseDone() throws ScanAbortedException, InterruptedException{
           while (isPaused() && canPause()){
               assertNotAborted();
               Thread.sleep(10);
           }
      }        
      
      public void stopAll() {
          for (Writable w : getWritables()) {
              try {
                  if (w instanceof Stoppable) {
                      ((Stoppable) w).stopAsync();
                  }
              } catch (Exception ex) {
                  logger.log(Level.WARNING, null, ex);
              }
          }
          
          if (monitors != null){
  