gt:         for (String variableName : variables) {
context: package org.esa.snap.opendap.utils;
  
  import com.bc.io.FileDownloader;
  import org.esa.snap.opendap.ui.DownloadProgressBarPM;
  import org.esa.snap.util.StringUtils;
  import ucar.ma2.Array;
  import ucar.ma2.InvalidRangeException;
  import ucar.nc2.Attribute;
  import ucar.nc2.Dimension;
  import ucar.nc2.FileWriter2;
  import ucar.nc2.NetcdfFile;
  import ucar.nc2.NetcdfFileWriter;
  import ucar.nc2.Variable;
  import ucar.nc2.dods.DODSNetcdfFile;
  import ucar.nc2.util.EscapeStrings;
  
  import java.io.File;
  import java.io.IOException;
  import java.net.URI;
  import java.net.URISyntaxException;
  import java.net.URL;
  import java.text.MessageFormat;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.GregorianCalendar;
  import java.util.List;
  import java.util.Map;
  
  public class DAPDownloader {
  
      private static final int MAX_FILENAME_DISPLAY_LENGTH = 15;
  
      final Map<String, Boolean> dapUris;
      final List<String> fileURIs;
      private final DownloadContext downloadContext;
      private final DownloadProgressBarPM pm;
  
      public DAPDownloader(Map<String, Boolean> dapUris, List<String> fileURIs, DownloadContext downloadContext,
                           DownloadProgressBarPM pm) {
          this.dapUris = dapUris;
          this.fileURIs = fileURIs;
          this.downloadContext = downloadContext;
          this.pm = pm;
      }
  
      public void saveProducts(File targetDir) throws IOException {
          if (targetDir != null && targetDir.isDirectory()) {
              downloadFilesWithDapAccess(targetDir);
              downloadFilesWithFileAccess(targetDir);
          } else {
              throw new IOException("No target directory specified.");
          }
      }
  
      private void downloadFilesWithDapAccess(File targetDir) throws IOException {
          for (Map.Entry<String, Boolean> entry : dapUris.entrySet()) {
              if (pm.isCanceled()) {
                  break;
              }
              downloadDapFile(targetDir, entry.getKey(), entry.getValue());
          }
      }
  
      private void downloadDapFile(File targetDir, String dapURI, boolean isLargeFile) throws IOException {
          String[] uriComponents = dapURI.split("\\?");
          String constraintExpression = "";
          String fileName = dapURI.substring(uriComponents[0].lastIndexOf("/") + 1);
          if (uriComponents.length > 1) {
              constraintExpression = uriComponents[1];
          }
          updateProgressBar(fileName, 0);
          DODSNetcdfFile netcdfFile = new DODSNetcdfFile(dapURI);
          writeNetcdfFile(targetDir, fileName, constraintExpression, netcdfFile, isLargeFile);
      }
  
      void writeNetcdfFile(File targetDir, String fileName, String constraintExpression, final DODSNetcdfFile sourceNetcdfFile, final boolean isLargeFile) throws IOException {
          final File file = new File(targetDir, fileName);
  
          if (file.exists() && !downloadContext.mayOverwrite(fileName)) {
              downloadContext.notifyFileDownloaded(file);
              updateProgressBar(fileName, (int) (file.length() / 1024));
              return;
          }
  
          if (StringUtils.isNullOrEmpty(constraintExpression)) {
              try {
                  Thread thread = new Thread(new Runnable() {
                      @Override
                      public void run() {
                          try {
                              FileWriter2 fileWriter = new FileWriter2(sourceNetcdfFile, file.getAbsolutePath(), NetcdfFileWriter.Version.netcdf3, null);
                              fileWriter.write();
                          } catch (IOException e) {
                              throw new RuntimeException(e);
                          }
                      }
                  });
                  thread.start();
                  int downloadedBefore = 0;
                  while (thread.isAlive()) {
                      try {
                          Thread.sleep(1000L);
                      } catch (InterruptedException ignore) {
                          // ignore
                      }
                      int downloaded = (int) (file.length() / 1024);
                      int delta = downloaded - downloadedBefore;
                      if (delta > 0) {
                          updateProgressBar(fileName, delta);
                          downloadedBefore = downloaded;
                      }
                  }
              } catch (RuntimeException e) {
                  if (e.getCause() instanceof IOException) {
                      throw (IOException) e.getCause();
                  } else {
                      throw e;
                  }
              }
  
              if (!pm.isCanceled()) {
                  downloadContext.notifyFileDownloaded(file);
              }
              return;
          }
          /**
           * algorithm:
           *   - get all variableNames vN from constraintExpression
           *   - get all dimensions d from sourceNetcdfFile
           *   - filter dimensions and variables
           *   - in new NetcdfFileWritable: create global attributes, dimensions and variables
           *   - create();
           *   - for all variables in new file:
           *      - get corresponding CE
           *      - array = sourceNetcdfFile.readWithCE();
           *      - variable.setData(array)
           *   - close();
           */
  
          final List<Variable> variables = sourceNetcdfFile.getVariables();
          final List<String> variableNames = new ArrayList<String>();
          for (Variable variable : variables) {
              variableNames.add(variable.getFullName());
          }
          final List<String> filteredVariables = filterVariables(variableNames, constraintExpression);
          final List<Dimension> filteredDimensions = filterDimensions(filteredVariables, sourceNetcdfFile);
  
          NetcdfFileWriter targetNetCDF = NetcdfFileWriter.createNew(NetcdfFileWriter.Version.netcdf3, file.getAbsolutePath());
          for (Dimension filteredDimension : filteredDimensions) {
              targetNetCDF.addDimension(null, filteredDimension.getFullName(), filteredDimension.getLength(),
                                        filteredDimension.isShared(), filteredDimension.isUnlimited(),
                                        filteredDimension.isVariableLength());
          }
          for (String filteredVariable : filteredVariables) {
              String varName = EscapeStrings.backslashEscape(filteredVariable, NetcdfFile.reservedSectionSpec);
              final Variable variable = sourceNetcdfFile.findVariable(varName);
              final Variable targetVariable = targetNetCDF.addVariable(null, variable.getFullName(), variable.getDataType(),
                                                                       variable.getDimensions());
              for (Attribute attribute : variable.getAttributes()) {
                  targetVariable.addAttribute(attribute);
              }
          }
          for (Attribute attribute : sourceNetcdfFile.getGlobalAttributes()) {
              targetNetCDF.addGroupAttribute(null, attribute);
          }
          targetNetCDF.create();
  
          for (String filteredVariable : filteredVariables) {
              String varName = EscapeStrings.backslashEscape(filteredVariable, NetcdfFile.reservedSectionSpec);
              final Variable sourceVariable = sourceNetcdfFile.findVariable(varName);
              String ceForVariable = getConstraintExpression(filteredVariable, constraintExpression);
              final Array values = sourceNetcdfFile.readWithCE(sourceVariable, ceForVariable);
              final int[] origin = getOrigin(filteredVariable, constraintExpression,
                                             sourceVariable.getDimensions().size());
              try {
                  targetNetCDF.write(sourceVariable, origin, values);
              } catch (InvalidRangeException e) {
                  throw new IOException(MessageFormat.format("Unable to download variable ''{0}'' into file ''{1}''.",
                                                             filteredVariable, fileName), e);
              }
          }
          targetNetCDF.close();
          downloadContext.notifyFileDownloaded(file);
      }
  
      private void updateProgressBar(String fileName, int work) {
          pm.worked(work);
          StringBuilder preMessageBuilder = new StringBuilder(fileName);
          int currentWork = pm.getCurrentWork();
          preMessageBuilder.append(" (")
                  .append(downloadContext.getAllDownloadedFilesCount() + 1)
                  .append("/")
                  .append(downloadContext.getAllFilesCount())
                  .append(")");
          if (currentWork != 0) {
              final long currentTime = new GregorianCalendar().getTimeInMillis();
              final long durationInMillis = currentTime - pm.getStartTime();
              double downloadSpeed = getDownloadSpeed(durationInMillis, currentWork);
              char sizeIdentifier = downloadSpeed < 1000 ? 'k' : 'M';
              downloadSpeed = downloadSpeed < 1000 ? downloadSpeed : downloadSpeed / 1024;
              String speedString = OpendapUtils.format(downloadSpeed);
              preMessageBuilder.append(" @ ").append(speedString).append(" ").append(sizeIdentifier).append("B/s");
          }
          int totalWork = pm.getTotalWork();
          double percentage = ((double) currentWork / totalWork) * 100.0;
          String workDone = OpendapUtils.format(currentWork / 1024.0);
          String totalWorkString = OpendapUtils.format(totalWork / 1024.0);
          pm.setPostMessage(workDone + " MB/" + totalWorkString + " MB (" + OpendapUtils.format(percentage) + "%)");
          String preMessageString = preMessageBuilder.toString();
          pm.setTooltip("Downloading " + preMessageString);
          final String shortenedFilename = fileName.substring(0, Math.min(fileName.length(), MAX_FILENAME_DISPLAY_LENGTH));
          pm.setPreMessage("Downloading " + preMessageString.replace(fileName, shortenedFilename + "..."));
      }
  
      static double getDownloadSpeed(long durationInMillis, int kilobyteCount) {
          return kilobyteCount / (durationInMillis / 1000.0);
      }
  
      static String getConstraintExpression(String sourceVariable, String constraintExpression) {
          final String[] constraintExpressions = constraintExpression.split(",");
          for (String expression : constraintExpressions) {
              if (expression.startsWith(sourceVariable + "[")) {
                  return expression;
              }
          }
          throw new IllegalArgumentException(
                  MessageFormat.format("Source variable ''{0}'' must be included in expression ''{1}''.",
                                       sourceVariable, constraintExpression));
      }
  
      static int[] getOrigin(String variableName, String constraintExpression, int dimensionCount) {
          int[] origin = new int[dimensionCount];
          Arrays.fill(origin, 0);
          if (StringUtils.isNullOrEmpty(constraintExpression)) {
              return origin;
          }
          final String[] variableConstraints = constraintExpression.split(",");
          for (String variableConstraint : variableConstraints) {
              if (variableConstraint.contains(variableName)) {
                  if (!variableConstraint.contains("[")) {
                      return origin;
                  }
  
                  variableConstraint = variableConstraint.replace("]", "");
                  String[] rangeConstraints = variableConstraint.split("\\[");
                  if (rangeConstraints.length - 1 > dimensionCount) {
                      throw new IllegalArgumentException(
                              MessageFormat.format("Illegal expression: ''{0}'' for variable ''{1}''.",
                                                   constraintExpression, variableName));
                  }
                  for (int i = 1; i < rangeConstraints.length; i++) {
                      String rangeConstraint = rangeConstraints[i];
                      String[] rangeComponents = rangeConstraint.split(":");
                      origin[i - 1] = Integer.parseInt(rangeComponents[0]);
                  }
  
              }
          }
          return origin;
      }
  
      static List<String> filterVariables(List<String> variableNames, String constraintExpression) {
          final List<String> filteredVariables = new ArrayList<String>();
          final List<String> constrainedVariableNames = getVariableNames(constraintExpression);
          if (constrainedVariableNames == null || constrainedVariableNames.isEmpty()) {
              return variableNames;
          }
          for (String variableName : variableNames) {
              if (constrainedVariableNames.contains(variableName)) {
                  filteredVariables.add(variableName);
              }
          }
          if (filteredVariables.isEmpty()) {
              return variableNames;
          }
          return filteredVariables;
      }
  
      static List<Dimension> filterDimensions(List<String> variables, NetcdfFile netcdfFile) {
          final List<Dimension> filteredDimensions = new ArrayList<Dimension>();
  
  