/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.stream;

import com.google.protobuf.ByteString;
import ucar.ma2.*;
import ucar.nc2.iosp.IospHelper;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * LOOK Not Done Yet
 *
 * @author caron
 * @since 10/26/2015.
 */
public class NcStreamDataRow {

  /*
  message DataRow {
    string fullName = 1;
    DataType dataType = 2;
    Section section = 3;
    bool bigend = 4;
    uint32 version = 5;
    bool isVlen = 7;
    uint32 nelems = 9;

    // oneof
    bytes primarray = 10;        // rectangular, primitive array # <1>
    repeated string stringdata = 11;  // string dataType # <2>
    ArrayStructureRow structdata = 12;  // structure/seq dataType # <3>
    repeated uint32 vlens = 13;  // isVlen true # <4>
    repeated bytes opaquedata = 14;  // opaque dataType # <5>
  }
  primarray has nelems * sizeof(dataType) bytes, turn into multidim array of primitives with section info and bigend
  stringdata has nelems strings, turn into multidim array of String with section info
  structdata has nelems StructureData objects, turn into multidim array of StructureData with section info and bigend
  vlens has section.size array lengths
  opaquedata has nelems opaque objects, turn into multidim array of Opaque with section info
*/

  public NcStreamProto.DataRow encodeData3(String name, boolean isVlen, Section section, Array data) {
    NcStreamProto.DataRow.Builder builder = NcStreamProto.DataRow.newBuilder();
    encodeData3(builder, name, isVlen, section, data);
    return builder.build();
  }

  void encodeData3(NcStreamProto.DataRow.Builder builder, String name, boolean isVlen, Section section, Array data) {
    DataType dataType = data.getDataType();

    builder.setFullName(name);
    builder.setDataType(NcStream.convertDataType(data.getDataType()));
    builder.setBigend(ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN);
    builder.setVersion(NcStream.ncstream_data_version);

    if (!isVlen) {
      builder.setNelems((int) data.getSize());
      builder.setSection(NcStream.encodeSection(section));
    }

    if (isVlen) {
      builder.setIsVlen(true);
      encodeVlenData(builder, section, data);

    } else if (dataType == DataType.STRING) {
      if (data instanceof ArrayChar) { // is this possible ?
        ArrayChar cdata =(ArrayChar) data;
        for (String s:cdata)
          builder.addStringdata(s);
        Section ssection = section.removeLast();
        builder.setSection(NcStream.encodeSection(ssection));

      } else if (data instanceof ArrayObject) {
        IndexIterator iter = data.getIndexIterator();
        while (iter.hasNext())
          builder.addStringdata( (String) iter.next());
      } else {
        throw new IllegalStateException("Unknown class for STRING ="+ data.getClass().getName());
      }

    } else if (dataType == DataType.OPAQUE) {
      if (data instanceof ArrayObject) {
        IndexIterator iter = data.getIndexIterator();
        while (iter.hasNext()) {
          ByteBuffer bb = (ByteBuffer) iter.next();
          builder.addOpaquedata(ByteString.copyFrom(bb));
        }
      } else {
        throw new IllegalStateException("Unknown class for OPAQUE ="+ data.getClass().getName());
      }

    } else if (dataType == DataType.STRUCTURE) {
      builder.setStructdata( encodeStructureData(name, data));

    } else if (dataType == DataType.SEQUENCE) {
      throw new UnsupportedOperationException("Not implemented yet SEQUENCE ="+ data.getClass().getName());

    } else { // normal case
      int nbytes = (int) data.getSizeBytes();
      ByteBuffer bb = ByteBuffer.allocate(nbytes);
      copyArrayToBB(data, bb);
      bb.flip();
      builder.setPrimdata(ByteString.copyFrom(bb));
    }

  }

  void encodeVlenData(NcStreamProto.DataRow.Builder builder, Section section, Array data) {
    if (!(data instanceof ArrayObject))
      throw new IllegalStateException("Unknown class for OPAQUE =" + data.getClass().getName());

    IndexIterator iter = data.getIndexIterator();
    int count = 0;
    while (iter.hasNext()) {
      Array varray = (Array) iter.next();
      int vlensize = (int) varray.getSize();
      builder.addVlens(vlensize);
      count += vlensize;
    }
    builder.setNelems(count);
    Section ssection = section.removeLast();
    builder.setSection(NcStream.encodeSection(ssection));
    assert section.computeSize() == count;

    int nbytes = count * data.getDataType().getSize();
    ByteBuffer bb = ByteBuffer.allocate(nbytes);

    // copyArrayToBB(data, bb);
    iter = data.getIndexIterator();
    while (iter.hasNext()) {
      Array varray = (Array) iter.next();
      copyArrayToBB(varray, bb);
    }
  }

  void copyArrayToBB(Array data, ByteBuffer out) {
    Class classType = data.getElementType();
    IndexIterator iterA = data.getIndexIterator();

    if (classType == double.class) {
      while (iterA.hasNext())
        out.putDouble(iterA.getDoubleNext());

    } else if (classType == float.class) {
      while (iterA.hasNext())
        out.putFloat(iterA.getFloatNext());

    } else if (classType == long.class) {
      while (iterA.hasNext())
        out.putLong(iterA.getLongNext());

    } else if (classType == int.class) {
      while (iterA.hasNext())
        out.putInt(iterA.getIntNext());

    } else if (classType == short.class) {
      while (iterA.hasNext())
        out.putShort(iterA.getShortNext());

    } else if (classType == char.class) {
      byte[] pa = IospHelper.convertCharToByte((char[]) data.get1DJavaArray(DataType.CHAR));
      out.put(pa, 0, pa.length);

    } else if (classType == byte.class) {
      while (iterA.hasNext())
        out.put(iterA.getByteNext());

    } else
      throw new UnsupportedOperationException("Class type = " + classType.getName());

  }

  private class MemberData {
    StructureMembers.Member member;
    int nelems;
    DataType dtype;
    boolean isVlen;

    ByteBuffer bb;
    List<String> stringList;
    List<ByteString> opaqueList;
    List<Array> vlenList;
    List<Integer> vlens;

    public MemberData(StructureMembers.Member member, int nelems) {
      this.member = member;
      this.nelems = nelems;
      this.dtype = member.getDataType();
      this.isVlen = member.isVariableLength();

      if (isVlen)
        vlenList = new ArrayList<>(nelems);
      else if (dtype == DataType.STRING)
        stringList = new ArrayList<>(nelems * member.getSize());
      else if (dtype == DataType.OPAQUE)
        opaqueList = new ArrayList<>(nelems * member.getSize());
      else if (dtype == DataType.STRUCTURE);
      else
        bb = ByteBuffer.allocate(nelems * member.getSizeBytes());
    }

    int completeVlens() {
      vlens = new ArrayList<>(nelems);
      int total = 0;
      for (Array va : vlenList) {
        vlens.add((int) va.getSize());
        total += va.getSize();
      }
      bb = ByteBuffer.allocate(nelems * member.getSizeBytes());
      for (Array va : vlenList) {
        copyArrayToBB(va, bb);
      }
      return total;
    }
  }

/*
  message Member {
    string shortName = 1;
    DataType dataType = 2;
    repeated uint32 shape = 3;  // or section?
    bool isVlen = 4;
  }

  message ArrayStructureRow {
    repeated Member members = 1;
    uint64 nrows = 5;      // number of rows in this message
    uint32 rowLength = 6;  // length in bytes of each row

    bytes fixdata = 10;            // fixed data
    repeated string stringdata = 11;  // string dataType
    repeated bytes bytedata = 13;  // opaque dataType and vlens
    repeated ArrayStructureRow structdata = 14;  // structure/seq dataType
  }
  */

  NcStreamProto.ArrayStructureRow.Builder encodeStructureData(String structName, Array data) {
    assert data instanceof ArrayStructure;
    ArrayStructure as = (ArrayStructure) data;
    int nelems = (int) as.getSize();

    List<MemberData> memberData = new ArrayList<>();
    StructureMembers sm = as.getStructureMembers();
    for (StructureMembers.Member m : sm.getMembers()) {
      memberData.add( new MemberData(m, nelems));
    }

    // use most efficient form of data extraction
    for (int recno=0; recno<nelems; recno++) {
      for (MemberData md : memberData) {
        if (md.member.isVariableLength()) {
          md.vlenList.add(as.getArray(recno, md.member));
        } else {
          extractData(as, recno, md);
        }
      }
    }

    NcStreamProto.ArrayStructureRow.Builder builder = NcStreamProto.ArrayStructureRow.newBuilder();
    for (MemberData md : memberData) {
      NcStreamProto.Member.Builder member = NcStreamProto.Member.newBuilder();
      member.setShortName(md.member.getName());
      member.setDataType(NcStream.convertDataType(md.member.getDataType()));
      /* LOOK
      member.setNelems(md.nelems);
      if (md.member.isVariableLength()) {
        md.completeVlens();
        nested.addAllVlens (md.vlens);
        nested.setPrimdata(ByteString.copyFrom(md.bb));

      } else if (md.member.getDataType() == DataType.STRING)
        nested.addAllStringdata(md.stringList);
      else if (md.member.getDataType() == DataType.OPAQUE)
        nested.addAllOpaquedata(md.opaqueList);
      else
        nested.setPrimdata(ByteString.copyFrom(md.bb)); */

      builder.addMembers(member);
    }

    return builder;
  }

  void extractData(ArrayStructure as, int recno, MemberData md) {
    StructureMembers.Member m = md.member;
    ByteBuffer bb = md.bb;
    Class classType = md.dtype.getPrimitiveClassType();

    if (m.isScalar()) {
      if (classType == double.class)
        bb.putDouble(as.getScalarDouble(recno, m));

      else if (classType == float.class)
        bb.putFloat(as.getScalarFloat(recno, m));

      else if (classType == byte.class)
        bb.put(as.getScalarByte(recno, m));

      else if (classType == short.class)
        bb.putShort(as.getScalarShort(recno, m));

      else if (classType == int.class)
        bb.putInt(as.getScalarInt(recno, m));

      else if (classType == long.class)
        bb.putLong(as.getScalarLong(recno, m));

      else if (md.dtype == DataType.CHAR)
        bb.put((byte) as.getScalarChar(recno, m));

      else if (md.dtype == DataType.STRING)
        md.stringList.add(as.getScalarString(recno, m));

      else if (md.dtype == DataType.OPAQUE)
        md.opaqueList.add( ByteString.copyFrom((ByteBuffer) as.getScalarObject(recno, m)));

    } else {
      if (classType == double.class) {
        double[] data = as.getJavaArrayDouble(recno, m);
        for (double aData : data) bb.putDouble(aData);

      } else if (classType == float.class) {
        float[] data = as.getJavaArrayFloat(recno, m);
        for (float aData : data) bb.putFloat(aData);

      } else if (classType == byte.class) {
        byte[] data = as.getJavaArrayByte(recno, m);
        for (byte aData : data) bb.put(aData);

      } else if (classType == short.class) {
        short[] data = as.getJavaArrayShort(recno, m);
        for (short aData : data) bb.putShort(aData);

      } else if (classType == int.class) {
        int[] data = as.getJavaArrayInt(recno, m);
        for (int aData : data) bb.putInt(aData);

      } else if (classType == long.class) {
        long[] data = as.getJavaArrayLong(recno, m);
        for (long aData : data) bb.putLong(aData);

      } else if (md.dtype == DataType.CHAR) {
        char[] data = as.getJavaArrayChar(recno, m);
        for (char aData : data) bb.put((byte) aData);

      } else if (md.dtype == DataType.STRING) {
        String[] data = as.getJavaArrayString(recno, m);
        Collections.addAll(md.stringList, data);

      } else if (md.dtype == DataType.OPAQUE) {
        ArrayObject ao = as.getArrayObject(recno, m);
        while (ao.hasNext()) md.opaqueList.add(ByteString.copyFrom((ByteBuffer) ao.next()));
      }
    }
  }
}


/* Copyright 2016, University Corporation for Atmospheric Research
   See the LICENSE.txt file for more information.
*/

package thredds.server.reify;

import org.apache.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import ucar.httpservices.HTTPUtil;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.Part;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;

/**
 * File upload to a specific directory.
 * As a rule, this needs to coordinate with
 * the upload catalog entry specified
 * in the catalog (see the documentation for upload/download).
 * The tag for this controller is .../upload/*
 *

 * The goal for this controller is to allow external code to have the
 * thredds server upload a file and store it in a specific place.
 *

 * Assumptions:
 * 1. The external code has authenticated to the thredds server
 * using some authentication mechanism such as
 * client-side certificates or tomcat roles.
 */

@Controller
@RequestMapping(value = {"/upload", "/restrictedAccess/upload"})
public class UploadController extends LoadCommon
{
    //////////////////////////////////////////////////
    // Constants

    static final protected boolean DEBUG = false;

    //////////////////////////////////////////////////
    // Instance variables

    protected String uploaddirname = null;
    protected String uploadform = null;
    protected Parameters params = null;
    protected boolean issetup = false;

    //////////////////////////////////////////////////
    // Constructor(s)

    public UploadController()
            throws ServletException
    {
        super();
    }

    /**
     * Invoked on first get request so that everything is available,
     * especially Spring stuff.
     */

    /**
     * Invoked once on first request so that everything is available,
     * especially Spring stuff.
     */
    public void
    doonce(HttpServletRequest req)
            throws SendError

    {
        if(once)
            return;
        super.initOnce(req);

        if(this.uploaddir == null)
            throw new SendError(HttpStatus.SC_PRECONDITION_FAILED, "Upload disabled");
        this.uploaddirname = new File(this.uploaddir).getName();

        // Get the upload form
        File upform = null;
        upform = tdsContext.getUploadForm();
        if(upform == null) {   // Look in WEB-INF directory
            File root = tdsContext.getServletRootDirectory();
            upform = new File(root, DEFAULTUPLOADFORM);
        }
	try {
	this.uploadform = loadForm(upform);
	} catch (IOException ioe) {
            throw new SendError(HttpStatus.SC_PRECONDITION_FAILED, ioe);
	}
    }

    // Setup for each request
    public void setup(HttpServletRequest req, HttpServletResponse resp)
            throws SendError
    {

        this.req = req;
        this.res = resp;
        if(!once)
            doonce(req);

        // Parse any query parameters
        try {
            this.params = new Parameters(req);
        } catch (IOException ioe) {
            throw new SendError(res.SC_BAD_REQUEST, ioe);
        }
    }

    //////////////////////////////////////////////////
    // Controller entry point(s)

    @RequestMapping(value = "**", method = RequestMethod.GET)
    public void
    doGet(HttpServletRequest req, HttpServletResponse res)
            throws ServletException
    {

        try {
            setup(req, res);

            switch (this.params.command) {
            case NONE:
            case UPLOAD:
                // Send back the upload form
                sendForm("No files uploaded");
                break;
            case INQUIRE:
                String result = inquire();
                // Send back the inquiry answers
                sendOK(result);
                break;
            default:
                throw new SendError(res.SC_BAD_REQUEST, "Unknown command: " + this.params.command);
            }
        } catch (SendError se) {
            sendError(se);
        } catch (Exception e) {
            String msg = getStackTrace(e);
            sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, msg, e);
        }
    }

    @RequestMapping(value = "**", method = RequestMethod.POST)
    public void
    doPost(HttpServletRequest req, HttpServletResponse res)
            throws ServletException
    {
        if(DEBUG)
            reportRequest(req);
        else
            try {
                setup(req, res);
                Collection<Part> parts = null;
                parts = req.getParts();
                if(parts.size() == 0) {
                    sendError(HttpStatus.SC_BAD_REQUEST, "UploadController: Empty request");
                    return;
                }
                String target = null;
                String filename = null;
                boolean overwrite = false;
                byte[] contents = null;
                for(Part part : parts) {
                    String field = part.getName();
                    String value = null;
                    InputStream stream = part.getInputStream();
                    if(field.equalsIgnoreCase("file")) {
                        value = HTTPUtil.nullify(part.getSubmittedFileName());
                        filename = value;
                        contents = HTTPUtil.readbinaryfile(stream);
                    } else
                        value = HTTPUtil.nullify(HTTPUtil.readtextfile(stream));
                    if(DEBUG)
                        System.err.printf("PART: %s=>%s%n", field, value);
                    if(field.equalsIgnoreCase("overwrite")) {
                        overwrite = (value != null && value.equalsIgnoreCase("true"));
                    } else if(field.equalsIgnoreCase("target")) {
                        target = value;
                    }  // else ignore
                }
                if(HTTPUtil.nullify(filename) == null) {
                    sendError(HttpStatus.SC_BAD_REQUEST, "Empty filename");
                    return;
                }
                if(target == null) {
                    // extract the basename
                    File t = new File(filename);
                    target = t.getName();
                }
                StringBuilder buf = new StringBuilder();
                buf.append(HTTPUtil.canonicalpath(this.uploaddir));
                buf.append("/");
                buf.append(target);
                String abstarget = HTTPUtil.canonicalpath(buf.toString());
                File targetfile = new File(abstarget);
                File targetdir = targetfile.getParentFile();
                if(!targetdir.exists() && !targetdir.mkdirs())
                    sendError(HttpStatus.SC_FORBIDDEN, "Cannot create target parent directory: " + target);
                if(targetfile.exists() && !overwrite)
                    sendError(HttpStatus.SC_FORBIDDEN, "Target exists and replace was not specified: " + target);
                if(targetfile.exists() && !targetfile.canWrite())
                    sendError(HttpStatus.SC_FORBIDDEN, "Target exists and is read-only: " + target);
                HTTPUtil.writebinaryfile(contents, targetfile);
                String msg = String.format("File upload succeeded: %s -> %s",
                        filename, target);
                sendOK(msg);
            } catch (IOException ioe) {
                sendError(HttpStatus.SC_NOT_FOUND, ioe.getMessage(), ioe);
            }
    }

    //////////////////////////////////////////////////

    @Override
    protected String
    buildForm(String msg)
    {
        StringBuilder svc = new StringBuilder();
        svc.append(this.server);
        svc.append("/");
        svc.append(this.threddsname);
        String form = String.format(this.uploadform,
                svc.toString(),
                msg,
                this.server,
                this.uploaddirname
        );
        return form;
    }

    @Override
    protected void
    sendReply(int code, String msg)
    {
        if(DEBUG) {
            System.err.printf("SendReply: code=%d%n%s%n", code, msg);
        }
        super.sendReply(code, msg);
    }

}

@ControllerAdvice
class ExceptionControllerAdvice
{
    @ExceptionHandler(HttpRequestMethodNotSupportedException.class)
    public String exception(HttpServletRequest rq, Exception e)
    {
        UploadController.log.error("HttpRequestMethodNotSupportedException");
        return "HttpRequestMethodNotSupportedException";
    }
}


/* Copyright 2009, UCAR/Unidata and OPeNDAP, Inc.
   See the COPYRIGHT file for more information. */

/****************************************************/

package opendap.servers.parsers;

import opendap.dap.*;
import opendap.servers.*;
import opendap.dap.parsers.*;
import ucar.nc2.util.EscapeStrings;

import java.util.*;

/**
 * The constraint expression parser class.

 *

 * Because it only makes sense to evaluate CEs when serving data, the
 * BaseTyeFactory must create instances of the SDtype classes, not
 * the Dtype classes. The is because we use the setRead method of the class
 * ServerMethods when creating constants (to ensure that the evaluator
 * doesn't try to read tem from the dataset!).
 *
 * @author (ofExprParser.jj) jhrg
 * @author Dennis Heimbigner
 */

public abstract class Ceparse implements ExprParserConstants
{
    //////////////////////////////////////////////////////////////////////////
    static public org.slf4j.Logger log
            = org.slf4j.LoggerFactory.getLogger(Ceparse.class);
     //////////////////////////////////////////////////////////////////////////

    BaseTypeFactory factory;

    Celex lexstate = null;

    Ceparse parsestate = null; /* Slight kludge */

    ASTconstraint ast = null;

    protected int cedebug = 0;

    List<AST> astnodeset = new ArrayList<AST>();

    /**
     * **********************************************
     */
    /* Constructor(s) */
    public Ceparse()
    {
        this(null);
    }

    public Ceparse(BaseTypeFactory factory)
    {
        parsestate = this;
        ast = new ASTconstraint(astnodeset);
        if(factory == null) {
            throw new RuntimeException("Ceparse: no factory specified");
        }
        this.factory = factory;
    }

    /**
     * **********************************************
     */
    /* Access into the CeParser for otherwise inaccessible fields */
    abstract public boolean parse() throws ParseException;

    abstract public int getDebugLevel();
    abstract public void setDebugLevel(int level);

    abstract public void setURL(String url);
    abstract public String getURL();

    abstract public void setConstraint(String constraint);
    abstract public String getConstraint();

    /**
     * **********************************************
     */
    /* Utilities */

    String strdup(String s)
    {
        return s;
    }

    void cleanup()
    {
        factory = null;
        lexstate = null;
    }

    /**
     * **********************************************
     */
    /* get/set */
    public AST getAST()
    {
        return ast;
    }

    public List<AST> getASTnodeset()
    {
        return astnodeset;
    }

    /**
     * **********************************************
     */
    /* Parser core */

    void
    projections(Ceparse state, Object list0)
            throws ParseException
    {
        ast.projections = (List<ASTprojection>) list0;
    }

    void
    selections(Ceparse state, Object list0)
            throws ParseException
    {
        ast.selections = (List<ASTclause>) list0;
    }

    Object
    projectionlist(Ceparse state, Object list0, Object decl)
            throws ParseException
    {
        List<ASTprojection> list = (List<ASTprojection>) list0;
        if (list == null) list = new ArrayList<ASTprojection>();
        list.add((ASTprojection) decl);
        return list;
    }

    Object
    projection(Ceparse state, Object varorfcn)
            throws ParseException
    {
        ASTprojection p = new ASTprojection(astnodeset);
            if(varorfcn instanceof ASTvar)
            p.var = (ASTvar)varorfcn;
        else
            p.fcn = (ASTfcn)varorfcn;
        return p;
    }

    Object
    segmentlist(Ceparse state, Object var0, Object decl)
            throws ParseException
    {
        ASTvar var = (ASTvar)var0;
        if(var == null) var = new ASTvar(astnodeset);
        var.segments.add((ASTsegment) decl);
        return var;
    }

    Object
    segment(Ceparse state, Object name, Object slices0)
            throws ParseException
    {
        ASTsegment segment = new ASTsegment(astnodeset);
        segment.name = (String) name;
        segment.slices = (List<ASTslice>) slices0;
        return segment;
    }

    Object
    rangelist(Ceparse state, Object list0, Object decl)
            throws ParseException
    {
        List<ASTslice> list = (List<ASTslice>) list0;
        if (list == null) list = new ArrayList<ASTslice>();
        list.add((ASTslice) decl);
        return list;
    }

    Object
    range(Ceparse state, Object sstart, Object sstride, Object sstop)
            throws ParseException
    {
        ASTslice slice = new ASTslice(astnodeset);
        int start = -1;
        int stride = -1;
        int stop = -1;
        /* Note: that incoming arguments are strings; we must convert to long;
           but we do know they are legal integers or null */
        try {
            start = Integer.parseInt((String) sstart);
        } catch (NumberFormatException nfe) {
        }
        ;
        if (sstop != null) {
            try {
                stop = Integer.parseInt((String) sstop);
            } catch (NumberFormatException nfe) {
            }
            ;
        } else
            stop = start;
        if (sstride != null) {
            try {
                stride = Integer.parseInt((String) sstride);
            } catch (NumberFormatException nfe) {
            }
            ;
        } else
            stride = 1; /* default */

        if (start < 0)
            throw new ParseException("Illegal index for range first index");
        if (stride <= 0)
            throw new ParseException("Illegal index for range stride index");
        if (stop < 0)
            throw new ParseException("Illegal index for range last index");

        if (stop < start)
            throw new ParseException("Range last index less than first index");

        slice.start = start;
        slice.stride = stride;
        slice.stop = stop;

        return slice;
    }

    Object range1(Ceparse state, Object rangenumber)
        throws ParseException
    {
        try {
            Long.parseLong((String)rangenumber);
        } catch (NumberFormatException nfe) {
            throw new ParseException("Index is not an integer");
        }
        return rangenumber;
    }

    /* Selection Procedures */

    Object
    clauselist(Ceparse state, Object list0, Object decl)
            throws ParseException
    {
        List<ASTclause> list = (List<ASTclause>) list0;
        if (list == null) list = new ArrayList<ASTclause>();
        list.add((ASTclause) decl);
        return list;
    }

    Object
    sel_clause(Ceparse state, int selcase,
               Object lhs, Object relop0, Object values)
            throws ParseException
    {
        ASTclause sel = new ASTclause(astnodeset);
        sel.lhs =  (ASTvalue)lhs;
        sel.operator = (Integer) relop0;
        sel.lhs = (ASTvalue)lhs;
        if (selcase == 2) { // singleton value
            sel.rhs = new ArrayList<ASTvalue>();
            sel.rhs.add((ASTvalue) values);
        } else
            sel.rhs = (List<ASTvalue>) values;
        return sel;
    }

    Object
    indexpath(Ceparse state, Object list0, Object index)
            throws ParseException
    {
        List<ASTsegment> list = (List<ASTsegment>) list0;
        if (list == null) list = new ArrayList<ASTsegment>();
        list.add((ASTsegment)index);
        return list;
    }

    Object
    array_indices(Ceparse state, Object list0, Object indexno)
            throws ParseException
    {
        List<ASTslice> list = (List<ASTslice>) list0;
        if (list == null) list = new ArrayList<ASTslice>();
        long start = 0;
        try {start = Long.parseLong((String)indexno);} catch (NumberFormatException nfe) {/*already checked*/};
        ASTslice slice = new ASTslice(astnodeset);
        slice.start = start;
        slice.stride = 1;
        slice.stop = start;
        list.add(slice);
        return list;
    }

    Object
    index(Ceparse state, Object name, Object indices)
            throws ParseException
    {
        ASTsegment segment = new ASTsegment(astnodeset);
        segment.name = (String) name;
        segment.slices = (List<ASTslice>)indices;
        return segment;
    }

    Object
    function(Ceparse state, Object fcnname, Object args)
            throws ParseException
    {
        ASTfcn fcn = new ASTfcn(astnodeset);
        fcn.fcnname = (String)fcnname;
        fcn.args = (List<ASTvalue>)args;
        return fcn;
    }

    Object
    arg_list(Ceparse state, Object list0, Object decl)
            throws ParseException
    {
        List<ASTvalue> list = (List<ASTvalue>) list0;
        if (list == null) list = new ArrayList<ASTvalue>();
        list.add((ASTvalue) decl);
        return list;
    }

    Object
    value_list(Ceparse state, Object list0, Object decl)
            throws ParseException
    {
        List<ASTvalue> list = (List<ASTvalue>) list0;
        if (list == null) list = new ArrayList<ASTvalue>();
        list.add((ASTvalue) decl);
        return list;
    }

    Object
    value(Ceparse state, Object o)
            throws ParseException
    {
        ASTvalue value = new ASTvalue(astnodeset);
        if(o instanceof ASTvar) value.var = (ASTvar)o;
        else if(o instanceof ASTfcn) value.fcn = (ASTfcn)o;
        else if(o instanceof ASTconstant) value.constant = (ASTconstant)o;
        return value;
    }

    Object
    var(Ceparse state, Object indexpath)
            throws ParseException
    {
        ASTvar var = new ASTvar(astnodeset);
        var.segments = (List<ASTsegment>) indexpath;
        return var;
    }

    Object
    constant(Ceparse state, Object path, int tag)
            throws ParseException
    {
        ASTconstant value = new ASTconstant(astnodeset);
        switch (tag) {
        case CeParser.Lexer.SCAN_STRINGCONST:
            value.text = (String) path;
            value.tag = STRINGCONST;
            break;
        case CeParser.Lexer.SCAN_NUMBERCONST:
            try {
                value.intvalue = Long.parseLong((String) path);
                value.tag = INTCONST;
            } catch (NumberFormatException nfe) {
                try {
                    value.floatvalue = Float.parseFloat((String) path);
                    value.tag = FLOATCONST;
                } catch (NumberFormatException nfe2) {
                    throw new ParseException("Illegal float constant");
                }
            }
            break;
        default:
            assert(false);
        }
        return value;
    }

    //////////////////////////////////////////////////
    // Utils

    String unescapeDAPName(Object name)
    {
        return EscapeStrings.unescapeDAPIdentifier((String) name);
    }

    //////////////////////////////////////////////////
    // Misc functions from original expr.jj

    /**
     * Run the named projection function. Projection functions are run for
     * their side effect; the return value is discarded.
     *
     * @param name The name of the projection function, look this up in the
     *             ServerDDS.
     * @param btv  A vector of BaseType variables that are the arguments of
     *             the projection function.
     */
    void runProjectionFunction(String name, Vector btv)
    {
    }

    /**
     * Remove double quotes from around a string. If there's not both start
     * and ending quotes, does nothing.
     *
     * @param s The source string.
     * @return The string without double quotes.
     */
    String removeQuotes(String s)
    {
        if (s.startsWith("\"") && s.endsWith("\""))
            return s.substring(1, s.length() - 1);
        else
            return s;
    }

    /**
     * Given a stack of BaseType variables, mark these as part of the
     * current projection. This function assumes that if the TOS contains a
     * Ctor type variable, all of its members are to be projected. Also
     * assume  all variables under the TOS are Ctor variables and
     * only the ctor itself is to be projected; the member within the Ctor
     * that is part of the projection will be on the stack, too.
     */
    void markStackedVariables(Stack s)
    {
        // Reverse the stack.
        Stack bts = new Stack();
        // LogStream.err.println("Variables to be marked:");
        while (!s.empty()) {
            // LogStream.err.println(((BaseType)s.peek()).getName());
            bts.push(s.pop());
        }

        // For each but the last stack element, set the projection.
        // setProject(true, false) for a ctor type sets the projection for
        // the ctor itself but *does not* set the projection for all its
        // children. Thus, if a user wants the variable S.X, and S contains Y
        // and Z too, S's projection will be set (so serialize will descend
        // into S) but X, Y and Z's projection remain clear. In this example,
        // X's projection is set by the code that follows the while loop.
        // 1/28/2000 jhrg
        while (bts.size() > 1) {
            ServerMethods ct = (ServerMethods) bts.pop();
            ct.setProject(true, false);
        }

        // For the last element, project the entire variable.
        ServerMethods bt = (ServerMethods) bts.pop();
        bt.setProject(true, true);
    }

    /**
     * *********************
     */
    // This parses, then fills in the evaluator from the AST
    public boolean constraint_expression(CEEvaluator ceEval,
                                         BaseTypeFactory factory,
                                         ClauseFactory clauseFactory)
            throws DAP2Exception, ParseException
    {
        ServerDDS sdds = ceEval.getDDS();
        if (!parse()) return false;
        ast.init(ceEval,factory,clauseFactory,sdds,getASTnodeset());
        ast.walkConstraint();
        return true;
    }

}


/*
 * Copyright (c) 1998-2019 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.ui.op;

import org.bounce.text.LineNumberMargin;
import org.bounce.text.ScrollableEditorPanel;
import org.bounce.text.xml.XMLDocument;
import org.bounce.text.xml.XMLEditorKit;
import org.bounce.text.xml.XMLStyleConstants;
import org.jdom2.Element;
import ucar.nc2.constants.CDM;
import ucar.nc2.dataset.NetcdfDataset;
import ucar.nc2.ncml.NcMLReader;
import ucar.nc2.ncml.NcMLWriter;
import ucar.nc2.ui.dialog.NetcdfOutputChooser;
import ucar.nc2.ui.widget.BAMutil;
import ucar.nc2.ui.widget.FileManager;
import ucar.nc2.ui.widget.IndependentWindow;
import ucar.nc2.ui.widget.TextHistoryPane;
import ucar.nc2.util.CancelTask;
import ucar.nc2.util.IO;
import ucar.nc2.write.Nc4ChunkingStrategy;
import ucar.util.prefs.PreferencesExt;
import ucar.util.prefs.ui.ComboBox;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Font;
import java.awt.Frame;
import java.awt.Rectangle;
import java.awt.event.ActionEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.Formatter;
import java.util.HashMap;
import java.util.Map;
import javax.swing.AbstractAction;
import javax.swing.AbstractButton;
import javax.swing.JEditorPane;
import javax.swing.JPanel;
import javax.swing.JOptionPane;
import javax.swing.JScrollPane;
import javax.swing.text.PlainDocument;

/**
 * Describe
 *
 * @author caron
 * @since 3/13/13
 */
public class NcmlEditor extends JPanel {
    private final static boolean debugNcmlWrite = false;

    private NetcdfDataset ds;
    private String ncmlLocation;
    private JEditorPane editor;
    private Map<String, String> protoMap = new HashMap<>(10);
    private ComboBox protoChooser;

    private TextHistoryPane infoTA;
    private IndependentWindow infoWindow;
    private FileManager fileChooser;
    private NetcdfOutputChooser outChooser;

    private final AbstractButton coordButt;
    private boolean addCoords;

    private PreferencesExt prefs;

/**
 *
 */
    public NcmlEditor(JPanel buttPanel, PreferencesExt prefs) {
        this.prefs = prefs;
        fileChooser = new FileManager(null, null, null, (PreferencesExt) prefs.node("FileManager"));

        final AbstractAction coordAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                addCoords = (Boolean) getValue(BAMutil.STATE);
                final String tooltip = addCoords ? "add Coordinates is ON" : "add Coordinates is OFF";
                coordButt.setToolTipText(tooltip);
            }
        };
        addCoords = prefs.getBoolean("coordState", false);
        final String tooltip2 = addCoords ? "add Coordinates is ON" : "add Coordinates is OFF";
        BAMutil.setActionProperties(coordAction, "addCoords", tooltip2, true, 'C', -1);
        coordAction.putValue(BAMutil.STATE, Boolean.valueOf(addCoords));
        coordButt = BAMutil.addActionToContainer(buttPanel, coordAction);

        protoChooser = new ComboBox((PreferencesExt) prefs.node("protoChooser"));
        addProtoChoices();
        buttPanel.add(protoChooser);
        protoChooser.addActionListener(e -> {
            String ptype = (String) protoChooser.getSelectedItem();
            String proto = protoMap.get(ptype);
            if (proto != null) {
                editor.setText(proto);
            }
        });

        editor = new JEditorPane();

        // Instantiate a XMLEditorKit with wrapping enabled.
        final XMLEditorKit kit = new XMLEditorKit(false);

        // Set the wrapping style.
        kit.setWrapStyleWord(true);

        editor.setEditorKit(kit);

        // Set the font style.
        editor.setFont(new Font("Monospaced", Font.PLAIN, 12));

        // Set the tab size
        editor.getDocument().putProperty(PlainDocument.tabSizeAttribute, 2);

        // Enable auto indentation.
        editor.getDocument().putProperty(XMLDocument.AUTO_INDENTATION_ATTRIBUTE, true);

        // Enable tag completion.
        editor.getDocument().putProperty(XMLDocument.TAG_COMPLETION_ATTRIBUTE, true);

        // Initialise the folding
        kit.setFolding(true);

        // Set a style
        kit.setStyle(XMLStyleConstants.ATTRIBUTE_NAME, Color.RED, Font.BOLD);

        // Put the editor in a panel that will force it to resize, when a different view is choosen.
        final ScrollableEditorPanel editorPanel = new ScrollableEditorPanel(editor);

        final JScrollPane scroller = new JScrollPane(editorPanel);

        // Add the number margin as a Row Header View
        scroller.setRowHeaderView(new LineNumberMargin(editor));

        final AbstractAction wrapAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                final XMLEditorKit kit = (XMLEditorKit) editor.getEditorKit();
                kit.setLineWrappingEnabled(!kit.isLineWrapping());
                editor.updateUI();
            }
        };
        BAMutil.setActionProperties(wrapAction, "Wrap", "Toggle Wrapping", false, 'W', -1);
        BAMutil.addActionToContainer(buttPanel, wrapAction);

        final AbstractAction saveAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                String location = (ds == null) ? ncmlLocation : ds.getLocation();
                if (location == null) { location = "test"; }

                int pos = location.lastIndexOf(".");
                if (pos > 0) {
                    location = location.substring(0, pos);
                }

                String filename = fileChooser.chooseFilenameToSave(location + ".ncml");
                if (filename == null)  { return; }
                if (doSaveNcml(editor.getText(), filename)) {
                    ncmlLocation = filename;
                }
            }
        };
        BAMutil.setActionProperties(saveAction, "Save", "Save NcML", false, 'S', -1);
        BAMutil.addActionToContainer(buttPanel, saveAction);

        final AbstractAction netcdfAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                if (outChooser == null) {
                    outChooser = new NetcdfOutputChooser((Frame) null);
                    outChooser.addPropertyChangeListener("OK", new PropertyChangeListener() {
                        public void propertyChange(PropertyChangeEvent evt) {
                            writeNetcdf((NetcdfOutputChooser.Data) evt.getNewValue());
                        }
                    });
                }

                String location = (ds == null) ? ncmlLocation : ds.getLocation();
                if (location == null) {
                    location = "test";
                }

                int pos = location.lastIndexOf(".");
                if (pos > 0) {
                    location = location.substring(0, pos);
                }
                outChooser.setOutputFilename(location);
                outChooser.setVisible(true);
            }
        };
        BAMutil.setActionProperties(netcdfAction, "netcdf", "Write netCDF file", false, 'N', -1);
        BAMutil.addActionToContainer(buttPanel, netcdfAction);

        final AbstractAction transAction = new AbstractAction() {
            @Override
            public void actionPerformed(ActionEvent e) {
                doTransform(editor.getText());
            }
        };
        BAMutil.setActionProperties(transAction, "Import", "read textArea through NcMLReader\n write NcML back out via resulting dataset", false, 'T', -1);
        BAMutil.addActionToContainer(buttPanel, transAction);

        final AbstractButton compareButton = BAMutil.makeButtcon("Select", "Check NcML", false);
        compareButton.addActionListener(e -> {
            Formatter f = new Formatter();
            checkNcml(f);

            infoTA.setText(f.toString());
            infoTA.gotoTop();
            infoWindow.show();
        });
        buttPanel.add(compareButton);

        setLayout(new BorderLayout());
        add(scroller, BorderLayout.CENTER);

        // the info window
        infoTA = new TextHistoryPane();
        infoWindow = new IndependentWindow("Extra Information", BAMutil.getImage("netcdfUI"), infoTA);
        infoWindow.setBounds((Rectangle) prefs.getBean("InfoWindowBounds", new Rectangle(300, 300, 500, 300)));
    }

/**
 *
 */
    public void save() {
        fileChooser.save();
        prefs.putBeanObject("InfoWindowBounds", infoWindow.getBounds());
    }

/**
 *
 */
    public void closeOpenFiles() {
        try {
            if (ds != null) { ds.close(); }
        }
        catch (IOException ioe) {
            // Nothing to do here.
        }
        ds = null;
   }

/**
 *
 */
    public boolean setNcml(String cmd) {
        if (cmd.endsWith(".xml") || cmd.endsWith(".ncml")) {
            if (!cmd.startsWith("http:") && !cmd.startsWith("file:")) {
                cmd = "file:" + cmd;
            }
            ncmlLocation = cmd;
            final String text = IO.readURLcontents(cmd);
            editor.setText(text);
        }
        else {
            writeNcml(cmd);
        }
        return true;
    }

/**
 * write ncml from given dataset
 */
    boolean writeNcml(String location) {
        boolean err = false;

        closeOpenFiles();

        try {
            final String result;
            ds = openDataset(location, addCoords, null);
            if (ds == null) {
                editor.setText("Failed to open <" + location + ">");
            }
            else {
                final NcMLWriter ncmlWriter = new NcMLWriter();
                final Element netcdfElem = ncmlWriter.makeNetcdfElement(ds, null);
                result = ncmlWriter.writeToString(netcdfElem);

                editor.setText(result);
                editor.setCaretPosition(0);
            }
        }
        catch (Exception e) {
            final StringWriter sw = new StringWriter(10000);
            e.printStackTrace();
            e.printStackTrace(new PrintWriter(sw));
            editor.setText(sw.toString());
            err = true;
        }

        return !err;
    }

/**
 *
 */
    private NetcdfDataset openDataset(String location, boolean addCoords, CancelTask task) {
        try {
            return NetcdfDataset.openDataset(location, addCoords, task);

            //if (setUseRecordStructure)
            //   ncd.sendIospMessage(NetcdfFile.IOSP_MESSAGE_ADD_RECORD_STRUCTURE);
        }
        catch (IOException ioe) {
            JOptionPane.showMessageDialog(null, "NetcdfDataset.open cannot open " + ioe.getMessage());
            if (! (ioe instanceof FileNotFoundException)) {
                ioe.printStackTrace();
            }
            return null;
        }
    }

/**
 *
 */
    void writeNetcdf(NetcdfOutputChooser.Data data) {
        //if (debugNcmlWrite) {
        //  System.out.printf("choices=%s%n", choice);
        //}

        String text = editor.getText();

        try {
          ByteArrayInputStream bis = new ByteArrayInputStream(text.getBytes(CDM.utf8Charset));
          NcMLReader.writeNcMLToFile(bis, data.outputFilename, data.version,
                        Nc4ChunkingStrategy.factory(data.chunkerType, data.deflate, data.shuffle)
          );
          JOptionPane.showMessageDialog(this, "File successfully written");

        }
        catch (Exception exc) {
            JOptionPane.showMessageDialog(this, "ERROR: " + exc.getMessage());
            exc.printStackTrace();
        }
    }

/**
 * Read text from textArea through NcMLReader
 * then write it back out via resulting dataset
 */
    void doTransform(String text) {
        try {
            final StringReader reader = new StringReader(text);
            final NetcdfDataset ncd = NcMLReader.readNcML(reader, null);
            final StringWriter sw = new StringWriter(10000);
            ncd.writeNcML(sw, null);
            editor.setText(sw.toString());
            editor.setCaretPosition(0);
            JOptionPane.showMessageDialog(this, "File successfully transformed");
        }
        catch (IOException ioe) {
            JOptionPane.showMessageDialog(this, "ERROR: " + ioe.getMessage());
            ioe.printStackTrace();
        }
    }

/**
 * Read text from textArea through NcMLReader
  *then write it back out via resulting dataset
  */
    private void checkNcml(Formatter f) {
        if (ncmlLocation == null) { return; }

        try {
            NetcdfDataset ncd = NetcdfDataset.openDataset(ncmlLocation);
            ncd.check(f);
        }
        catch (IOException ioe) {
            JOptionPane.showMessageDialog(this, "ERROR: " + ioe.getMessage());
            ioe.printStackTrace();
        }
    }

/**
 *
 */
    boolean doSaveNcml(String text, String filename) {
        if (debugNcmlWrite) {
            System.out.println("filename=" + filename);
            System.out.println("text=" + text);
        }

        File out = new File(filename);
        if (out.exists()) {
            int val = JOptionPane.showConfirmDialog(null,
                    filename + " already exists. Do you want to overwrite?", "WARNING",
                  JOptionPane.YES_NO_OPTION);
            if (val != JOptionPane.YES_OPTION) { return false; }
        }

        try {
            IO.writeToFile(text, out);
            JOptionPane.showMessageDialog(this, "File successfully written");
            return true;
        }
        catch (IOException ioe) {
            JOptionPane.showMessageDialog(this, "ERROR: " + ioe.getMessage());
            ioe.printStackTrace();
            return false;
        }
        // saveNcmlDialog.setVisible(false);
    }

/**
 *
 */
    void addProtoChoices() {
        String xml =
                "<?xml version='1.0' encoding='UTF-8'?>\n" +
                        "<netcdf xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
                        "  <variable name='time' type='int' shape='time'>\n" +
                        "    <attribute name='long_name' type='string' value='time coordinate' />\n" +
                        "    <attribute name='units' type='string' value='days since 2001-8-31 00:00:00 UTC' />\n" +
                        "    <values start='0' increment='10' />\n" +
                        "  </variable>\n" +
                        "  <aggregation dimName='time' type='joinNew'>\n" +
                        "    <variableAgg name='T'/>\n" +
                        "    <scan location='src/test/data/ncml/nc/' suffix='.nc' subdirs='false'/>\n" +
                        "  </aggregation>\n" +
                        "</netcdf>";
        protoMap.put("joinNew", xml);
        protoChooser.addItem("joinNew");

        xml = "<?xml version='1.0' encoding='UTF-8'?>\n" +
                        "<netcdf xmlns='http://www.unidata.ucar.edu/namespaces/netcdf/ncml-2.2'>\n" +
                        "  <aggregation dimName='time' type='joinExisting'>\n" +
                        "    <scan location='ncml/nc/pfeg/' suffix='.nc' />\n" +
                        "  </aggregation>\n" +
                        "</netcdf>";
        protoMap.put("joinExisting", xml);
        protoChooser.addItem("joinExisting");
    }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package thredds.server.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

/**
 * Centralize the mapping of threddsConfig.xml configuration settings to the data objects used by
 * the various servlets. Supports earlier versions (some deprecated) of threddsConfig.xml config settings.
 * <p/>
 * called from TdsInit
 *
 * @author edavis
 * @since 4.1
 */
@Component
class TdsConfigMapper {
  // ToDo Not yet using ThreddsConfig.get<type>() methods.

  @Autowired
  private TdsServerInfoBean tdsServerInfo;
  @Autowired
  private HtmlConfigBean htmlConfig;
  @Autowired
  private WmsConfigBean wmsConfig;
  @Autowired
  private CorsConfigBean corsConfig;
  @Autowired
  private TdsUpdateConfigBean tdsUpdateConfig;

  // static so can be called from static enum classes
  private static String getValueFromThreddsConfig(String key, String alternateKey, String defaultValue) {
    String value = ThreddsConfig.get(key, null);
    if (value == null && alternateKey != null)
      value = ThreddsConfig.get(alternateKey, null);
    if (value == null)
      value = defaultValue;
    return value;
  }

  enum ServerInfoMappings {
    SERVER_NAME("serverInformation.name", "htmlSetup.installName", "Initial TDS Installation"),
    SERVER_LOGO_URL("serverInformation.logoUrl", "htmlSetup.installLogoUrl", "threddsIcon.png"),
    SERVER_LOGO_ALT_TEXT("serverInformation.logoAltText", "htmlSetup.installLogoAlt", "Initial TDS Installation"),
    SERVER_ABSTRACT("serverInformation.abstract", null, "Scientific Data"),
    SERVER_KEYWORDS("serverInformation.keywords", null, "meteorology, atmosphere, climate, ocean, earth science"),
    SERVER_CONTACT_NAME("serverInformation.contact.name", null, ""),
    SERVER_CONTACT_ORGANIZATION("serverInformation.contact.organization", null, ""),
    SERVER_CONTACT_EMAIL("serverInformation.contact.email", null, ""),
    SERVER_CONTACT_PHONE("serverInformation.contact.phone", null, ""),
    SERVER_HOST_INSTITUTION_NAME("serverInformation.hostInstitution.name", "htmlSetup.hostInstName", ""),
    SERVER_HOST_INSTITUTION_WEBSITE("serverInformation.hostInstitution.webSite", "htmlSetup.hostInstUrl", ""),
    SERVER_HOST_INSTITUTION_LOGO_URL("serverInformation.hostInstitution.logoUrl", "htmlSetup.hostInstLogoUrl", ""),
    SERVER_HOST_INSTITUTION_LOGO_ALT_TEXT("serverInformation.hostInstitution.logoAltText", "htmlSetup.hostInstLogoAlt", "");

    private String key;
    private String alternateKey;  // deprecated
    private String defaultValue;

    ServerInfoMappings(String key, String alternateKey, String defaultValue) {
      if (key == null || defaultValue == null)
        throw new IllegalArgumentException("The key and default value may not be null.");

      this.key = key;
      this.alternateKey = alternateKey;
      this.defaultValue = defaultValue;
    }

    String getValueFromThreddsConfig() {
      return TdsConfigMapper.getValueFromThreddsConfig(this.key, this.alternateKey, this.defaultValue);
    }

    static void load(TdsServerInfoBean info) {
      info.setName(SERVER_NAME.getValueFromThreddsConfig());
      info.setLogoUrl(SERVER_LOGO_URL.getValueFromThreddsConfig());
      info.setLogoAltText(SERVER_LOGO_ALT_TEXT.getValueFromThreddsConfig());
      info.setSummary(SERVER_ABSTRACT.getValueFromThreddsConfig());
      info.setKeywords(SERVER_KEYWORDS.getValueFromThreddsConfig());

      info.setContactName(SERVER_CONTACT_NAME.getValueFromThreddsConfig());
      info.setContactOrganization(SERVER_CONTACT_ORGANIZATION.getValueFromThreddsConfig());
      info.setContactEmail(SERVER_CONTACT_EMAIL.getValueFromThreddsConfig());
      info.setContactPhone(SERVER_CONTACT_PHONE.getValueFromThreddsConfig());

      info.setHostInstitutionName(SERVER_HOST_INSTITUTION_NAME.getValueFromThreddsConfig());
      info.setHostInstitutionWebSite(SERVER_HOST_INSTITUTION_WEBSITE.getValueFromThreddsConfig());
      info.setHostInstitutionLogoUrl(SERVER_HOST_INSTITUTION_LOGO_URL.getValueFromThreddsConfig());
      info.setHostInstitutionLogoAltText(SERVER_HOST_INSTITUTION_LOGO_ALT_TEXT.getValueFromThreddsConfig());
    }
  }

  enum HtmlConfigMappings {
    HTML_STANDARD_CSS_URL("htmlSetup.standardCssUrl", null, ""),
    HTML_CATALOG_CSS_URL("htmlSetup.catalogCssUrl", null, ""),
    HTML_DATASET_CSS_URL("htmlSetup.datasetCssUrl", null, ""),
    HTML_OPENDAP_CSS_URL("htmlSetup.openDapCssUrl", null, "tdsDap.css"),
    GOOGLE_TRACKING_CODE("htmlSetup.googleTrackingCode", null, ""),

    HTML_FOLDER_ICON_URL("htmlSetup.folderIconUrl", null, "folder.gif"),
    HTML_FOLDER_ICON_ALT("htmlSetup.folderIconAlt", null, "Folder"),
    HTML_DATASET_ICON_URL("htmlSetup.datasetIconUrl", null, ""),
    HTML_DATASET_ICON_ALT("htmlSetup.datasetIconAlt", null, ""),
    HTML_USE_REMOTE_CAT_SERVICE("htmlSetup.useRemoteCatalogService", null, "true"),
    HTML_GENERATE_DATASET_JSON_LD("htmlSetup.generateDatasetJsonLD", null, "false");

    private String key;
    private String alternateKey;
    private String defaultValue;

    HtmlConfigMappings(String key, String alternateKey, String defaultValue) {
      if (key == null || defaultValue == null)
        throw new IllegalArgumentException("The key and default value may not be null.");

      this.key = key;
      this.alternateKey = alternateKey;
      this.defaultValue = defaultValue;
    }

    String getValueFromThreddsConfig() {
      return TdsConfigMapper.getValueFromThreddsConfig(this.key, this.alternateKey, this.defaultValue);
    }

    static void load(HtmlConfigBean htmlConfig, TdsContext tdsContext, TdsServerInfoBean serverInfo) {
      htmlConfig.init(tdsContext.getWebappDisplayName(), tdsContext.getWebappVersion(), tdsContext.getTdsVersionBuildDate(), tdsContext.getContextPath());

      htmlConfig.setInstallName(serverInfo.getName());
      htmlConfig.setInstallLogoUrl(serverInfo.getLogoUrl());
      htmlConfig.setInstallLogoAlt(serverInfo.getLogoAltText());

      htmlConfig.setHostInstName(serverInfo.getHostInstitutionName());
      htmlConfig.setHostInstUrl(serverInfo.getHostInstitutionWebSite());
      htmlConfig.setHostInstLogoUrl(serverInfo.getHostInstitutionLogoUrl());
      htmlConfig.setHostInstLogoAlt(serverInfo.getHostInstitutionLogoAltText());

      htmlConfig.setPageCssUrl(HTML_STANDARD_CSS_URL.getValueFromThreddsConfig());
      htmlConfig.setCatalogCssUrl(HTML_CATALOG_CSS_URL.getValueFromThreddsConfig());
      htmlConfig.setDatasetCssUrl(HTML_DATASET_CSS_URL.getValueFromThreddsConfig());
      htmlConfig.setOpenDapCssUrl(HTML_OPENDAP_CSS_URL.getValueFromThreddsConfig());
      htmlConfig.setGoogleTrackingCode(GOOGLE_TRACKING_CODE.getValueFromThreddsConfig());

      htmlConfig.setFolderIconUrl(HTML_FOLDER_ICON_URL.getValueFromThreddsConfig());
      htmlConfig.setFolderIconAlt(HTML_FOLDER_ICON_ALT.getValueFromThreddsConfig());
      htmlConfig.setDatasetIconUrl(HTML_DATASET_ICON_URL.getValueFromThreddsConfig());
      htmlConfig.setDatasetIconAlt(HTML_DATASET_ICON_ALT.getValueFromThreddsConfig());

      htmlConfig.setUseRemoteCatalogService(Boolean.parseBoolean(HTML_USE_REMOTE_CAT_SERVICE.getValueFromThreddsConfig()));
      htmlConfig.setGenerateDatasetJsonLD(Boolean.parseBoolean(HTML_GENERATE_DATASET_JSON_LD.getValueFromThreddsConfig()));
    }
  }

  enum WmsConfigMappings {
    WMS_ALLOW("WMS.allow", null, "false"),
    WMS_ALLOW_REMOTE("WMS.allowRemote", null, "false"),
    WMS_PALETTE_LOCATION_DIR("WMS.paletteLocationDir", null, null),
    WMS_MAXIMUM_IMAGE_WIDTH("WMS.maxImageWidth", null, "2048"),
    WMS_MAXIMUM_IMAGE_HEIGHT("WMS.maxImageHeight", null, "2048");

    private String key;
    private String alternateKey;
    private String defaultValue;

    WmsConfigMappings(String key, String alternateKey, String defaultValue) {
      if (key == null)
        throw new IllegalArgumentException("The key may not be null.");

      this.key = key;
      this.alternateKey = alternateKey;
      this.defaultValue = defaultValue;
    }

    String getDefaultValue() {
      return this.defaultValue;
    }

    String getValueFromThreddsConfig() {
      return TdsConfigMapper.getValueFromThreddsConfig(this.key, this.alternateKey, this.defaultValue);
    }

    static void load(WmsConfigBean wmsConfig) {
      wmsConfig.setAllow(Boolean.parseBoolean(WMS_ALLOW.getValueFromThreddsConfig()));
      wmsConfig.setAllowRemote(Boolean.parseBoolean(WMS_ALLOW_REMOTE.getValueFromThreddsConfig()));
      wmsConfig.setPaletteLocationDir(WMS_PALETTE_LOCATION_DIR.getValueFromThreddsConfig());

      try {
        wmsConfig.setMaxImageWidth(Integer.parseInt(WMS_MAXIMUM_IMAGE_WIDTH.getValueFromThreddsConfig()));
      } catch (NumberFormatException e) {
        // If the given maxImageWidth value is not a number, try the default value.
        wmsConfig.setMaxImageWidth(Integer.parseInt(WMS_MAXIMUM_IMAGE_WIDTH.getDefaultValue()));
      }
      try {
        wmsConfig.setMaxImageHeight(Integer.parseInt(WMS_MAXIMUM_IMAGE_HEIGHT.getValueFromThreddsConfig()));
      } catch (NumberFormatException e) {
        // If the given maxImageHeight value is not a number, try the default value.
        wmsConfig.setMaxImageHeight(Integer.parseInt(WMS_MAXIMUM_IMAGE_HEIGHT.getDefaultValue()));
      }
    }
  }

  enum CorsConfigMappings {
    CORS_ENABLED("CORS.enabled", null, "false"),
    CORS_MAXIMUM_AGE("CORS.maxAge", null, "1728000"),
    CORS_ALLOWED_METHODS("CORS.allowedMethods", null, "GET"),
    CORS_ALLOWED_HEADERS("CORS.allowedHeaders", null, "Authorization"),
    CORS_ALLOWED_ORIGIN("CORS.allowedOrigin", null, "*");

    private String key;
    private String alternateKey;
    private String defaultValue;

    CorsConfigMappings(String key, String alternateKey,
                       String defaultValue) {
      if (key == null)
        throw new IllegalArgumentException("The key may not be null.");

      this.key = key;
      this.alternateKey = alternateKey;
      this.defaultValue = defaultValue;
    }

    String getDefaultValue() {
      return this.defaultValue;
    }

    String getValueFromThreddsConfig() {
      return TdsConfigMapper.getValueFromThreddsConfig(this.key, this.alternateKey, this.defaultValue);
    }

    static void load(CorsConfigBean corsConfig) {
       corsConfig.setEnabled(Boolean.parseBoolean(CORS_ENABLED.getValueFromThreddsConfig()));
       try {
         corsConfig.setMaxAge(Integer.parseInt(CORS_MAXIMUM_AGE.getValueFromThreddsConfig()));
       } catch (NumberFormatException e) {
         corsConfig.setMaxAge(Integer.parseInt(CORS_MAXIMUM_AGE.getDefaultValue()));
       }
       corsConfig.setAllowedHeaders(CORS_ALLOWED_HEADERS.getValueFromThreddsConfig());
       corsConfig.setAllowedMethods(CORS_ALLOWED_METHODS.getValueFromThreddsConfig());
       corsConfig.setAllowedOrigin(CORS_ALLOWED_ORIGIN.getValueFromThreddsConfig());
     }
  }

  enum TdsUpdateConfigMappings {
    TDSUPDAATE_LOGVERSIONINFO("TdsUpdateConfig.logVersionInfo", null, "true");

    private String key;
    private String alternateKey;
    private String defaultValue;

    TdsUpdateConfigMappings(String key, String alternateKey, String defaultValue) {
      if (key == null) {
        throw new IllegalArgumentException("The key may not be null.");
      }

      this.key = key;
      this.alternateKey = alternateKey;
      this.defaultValue = defaultValue;
    }

    String getDefaultValue() {
      return this.defaultValue;
    }

    String getValueFromThreddsConfig() {
      return TdsConfigMapper.getValueFromThreddsConfig(this.key, this.alternateKey, this.defaultValue);
    }

    static void load(TdsUpdateConfigBean tdsUpdateConfig) {
      tdsUpdateConfig.setLogVersionInfo(Boolean.parseBoolean(TDSUPDAATE_LOGVERSIONINFO.getValueFromThreddsConfig()));
    }
  }

  /////////////////////////////////////////////////////////////////////

  TdsConfigMapper() {
  }

  void init(TdsContext tdsContext) {
    ServerInfoMappings.load(tdsServerInfo);
    HtmlConfigMappings.load(htmlConfig, tdsContext, tdsServerInfo);
    WmsConfigMappings.load(wmsConfig);
    CorsConfigMappings.load(corsConfig);
    TdsUpdateConfigMappings.load(tdsUpdateConfig);
  }

}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2.grib.collection;

import javax.annotation.Nullable;
import thredds.featurecollection.FeatureCollectionConfig;
import ucar.nc2.time.CalendarDate;
import ucar.unidata.io.RandomAccessFile;

import java.util.List;

/**
 * Read Grib2Partition From ncx Index,
 * Data file never opened.
 *
 * @author John
 * @since 12/7/13
 */
public class Grib2PartitionBuilderFromIndex extends Grib2CollectionBuilderFromIndex {

  // read in the index, index raf already open; return null on failure
  @Nullable
  static Grib2Partition createTimePartitionFromIndex(String name, RandomAccessFile raf,
      FeatureCollectionConfig config, org.slf4j.Logger logger) {

    Grib2PartitionBuilderFromIndex builder = new Grib2PartitionBuilderFromIndex(name, config, logger);
    if (builder.readIndex(raf))
      return new Grib2Partition(builder.pc);

    return null;
  }

  // read in the index, index raf already open; return null on failure
  @Nullable
  static PartitionCollectionMutable openMutablePCFromIndex(String name, RandomAccessFile raf,
      FeatureCollectionConfig config, org.slf4j.Logger logger) {

    Grib2PartitionBuilderFromIndex builder = new Grib2PartitionBuilderFromIndex(name, config, logger);
    if (builder.readIndex(raf))
      return builder.pc;

    return null;
  }

  //////////////////////////////////////////////////////////////////////////////////

  private final PartitionCollectionMutable pc;  // build this object

  private Grib2PartitionBuilderFromIndex(String name, FeatureCollectionConfig config, org.slf4j.Logger logger) {
    super(name, config, logger);
    this.pc = new PartitionCollectionMutable(name, null, config, false, logger);
    this.gc = pc;
  }

  @Override
  public String getMagicStart() {
    return Grib2PartitionBuilder.MAGIC_START;
  }

  ///////////////////////////////////////////////////////////////////////////
  // reading ncx

  /*
  extend GribCollection {
    repeated Partition partitions = 100;
    required bool isPartitionOfPartitions = 101;
    repeated uint32 run2part = 102;       // masterRuntime index to partition index
  }
   */
  @Override
  protected boolean readExtensions(GribCollectionProto.GribCollection proto) {
    pc.isPartitionOfPartitions = proto.getIsPartitionOfPartitions();

    List<Integer> list = proto.getRun2PartList();
    pc.run2part = new int[list.size()];
    int count = 0;
    for (int partno : list)
      pc.run2part[count++] = partno;

    List<ucar.nc2.grib.collection.GribCollectionProto.Partition> partList = proto.getPartitionsList();
    for (ucar.nc2.grib.collection.GribCollectionProto.Partition partProto : partList)
      makePartition(partProto);

    return partList.size() > 0;
  }

  /*
  extend Variable {
    repeated PartitionVariable partition = 100;
    repeated Parameter vparams = 101;    // not used yet
  }
   */

  @Override
  protected GribCollectionMutable.VariableIndex readVariableExtensions(GribCollectionMutable.GroupGC group, GribCollectionProto.Variable proto, GribCollectionMutable.VariableIndex vi) {
    List<GribCollectionProto.PartitionVariable> pvList = proto.getPartVariableList();
    PartitionCollectionMutable.VariableIndexPartitioned vip = pc.makeVariableIndexPartitioned(group, vi, pvList.size());
    vip.setPartitions(pvList);

    // cant put this in the constructor
    vip.ndups = vi.ndups;
    vip.nrecords = vi.nrecords;
    vip.nmissing = vi.nmissing;
    return vip;
  }

  /*
message Partition {
  required string name = 1;       // name is used in TDS - eg the subdirectory when generated by TimePartitionCollections
  required string filename = 2;   // the gribCollection.ncx2 file
  required string directory = 3;   // top directory
  optional uint64 lastModified = 4;
}
   */
  private PartitionCollectionMutable.Partition makePartition(GribCollectionProto.Partition proto) {
    long partitionDateMillisecs =  proto.getPartitionDate();
    CalendarDate partitionDate =  partitionDateMillisecs > 0 ? CalendarDate.of(partitionDateMillisecs) : null;
    return pc.addPartition(proto.getName(), proto.getFilename(), proto.getLastModified(), proto.getLength(), null, partitionDate);
  }

}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */

package ucar.nc2;

import ucar.ma2.DataType;
import ucar.nc2.util.Indent;

import javax.annotation.concurrent.Immutable;
import java.util.*;

/**
 * Enumeration Typedef map integers to Strings.
 * For ENUM1, ENUM2, ENUM4 enumeration types.
 * Immutable.
 *
 * @author caron
 */
@Immutable
public class EnumTypedef extends CDMNode {

  // Constants for the unsigned max values for enum(1,2,4)
  static public final int UBYTE_MAX = 255;
  static public final int USHORT_MAX = 65535;
  //not used static public final long UINT_MAX = 4294967295L;

  private final Map<Integer, String> map;
  private final ArrayList<String> enumStrings;
  private final DataType basetype;

  public EnumTypedef(String name, Map<Integer, String> map) {
    this(name, map, DataType.ENUM4); //default basetype
  }

  public EnumTypedef(String name, Map<Integer, String> map, DataType basetype) {
    super(name);
    assert (validateMap(map, basetype));
    this.map = map;

    enumStrings = new ArrayList<>(map.values());
    Collections.sort(enumStrings);

    assert basetype == DataType.ENUM1 || basetype == DataType.ENUM2 || basetype == DataType.ENUM4;
    this.basetype = basetype;
  }

  public List<String> getEnumStrings() {
    return enumStrings;
  }

  public Map<Integer, String> getMap() {
    return map;
  }

  public DataType getBaseType() {
    return this.basetype;
  }

  public boolean
  validateMap(Map<Integer, String> map, DataType basetype) {
    if (map == null || basetype == null) return false;
    for (Integer i : map.keySet()) {
      // WARNING, we do not have signed/unsigned info available
      switch (basetype) {
        case ENUM1:
          if (i < Byte.MIN_VALUE || i > UBYTE_MAX)
            return false;
          break;
        case ENUM2:
          if (i < Short.MIN_VALUE || i > USHORT_MAX)
            return false;
          break;
        case ENUM4:
          break; // enum4 is always ok
        default:
          return false;
      }
    }
    return true;
  }

  /* private boolean
  IgnoreinRange(int i) {
    // WARNING, we do not have signed/unsigned info available
    if (this.basetype == DataType.ENUM1
            && (i >= Byte.MIN_VALUE || i <= UBYTE_MAX))
      return true;
    else if (this.basetype == DataType.ENUM2
            && (i >= Short.MIN_VALUE || i <= USHORT_MAX))
      return true;
    else if (this.basetype == DataType.ENUM4) // always ok
      return true;
    else
      return false;
  } */

  public String lookupEnumString(int e) {
    String result = map.get(e);
    return (result == null) ? "Unknown enum value=" + e : result;
  }

  public Integer
  lookupEnumInt(String name)
  {
     for(Map.Entry<Integer,String> entry: map.entrySet()) {
       if(entry.getValue().equalsIgnoreCase(name))
         return entry.getKey();
     }
    return null;
  }

  /**
   * String representation.
   *
   * @param strict if true, write in strict adherence to CDL definition.
   * @return CDL representation.
   */
  public String writeCDL(boolean strict) {
    Formatter out = new Formatter();
    writeCDL(out, new Indent(2), strict);
    return out.toString();
  }

  protected void writeCDL(Formatter out, Indent indent, boolean strict) {
    String name = strict ? NetcdfFile.makeValidCDLName(getShortName()) : getShortName();
    String basetype = "";
    switch (this.basetype) {
      case ENUM1:
        basetype = "byte ";
        break;
      case ENUM2:
        basetype = "short ";
        break;
      case ENUM4:
        basetype = "";
        break;
      default:
        assert false : "Internal error";
    }
    out.format("%s%senum %s { ", indent, basetype, name);
    int count = 0;
    // List<Object> keyset = Arrays.asList(map.keySet().toArray());
    List<Integer> keysetList = new ArrayList<>(map.keySet());
    Collections.sort(keysetList);
    for (Integer key : keysetList) {
      String s = map.get(key);
      if (0 < count++) out.format(", ");
      if (strict)
        out.format("%s = %s", NetcdfFile.makeValidCDLName(s), key);
      else
        out.format("'%s' = %s", s, key);
    }
    out.format("};");
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    EnumTypedef that = (EnumTypedef) o;

    if (map == that.map) return true;
    if (map == null) return false;
    if (!map.equals(that.map)) return false;
    String name = getShortName();
    String thatname = that.getShortName();
    return !(name != null ? !name.equals(thatname) : thatname != null);

  }

  @Override
  public int hashCode() {
    String name = getShortName();
    int result = name != null ? name.hashCode() : 0;
    result = 31 * result + (map != null ? map.hashCode() : 0);
    return result;
  }

  @Override
  public void hashCodeShow(Indent indent) {
    System.out.printf("%sEnum hash = %d%n", indent, hashCode());
    System.out.printf("%s shortName '%s' = %d%n", indent, getShortName(), getShortName() == null ? -1 : getShortName().hashCode());
    System.out.printf("%s map = %s%n", indent, map.hashCode());
  }

  @Override
  public String toString() {
    final Formatter f = new Formatter();
    f.format("EnumTypedef %s: ", getShortName());
    for (int key : map.keySet()) {
      f.format("%d=%s,", key, map.get(key));
    }
    return f.toString();
  }
}


/*
 * Copyright (c) 1998-2018 John Caron and University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
package ucar.nc2.ft.point.remote;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import ucar.ma2.StructureData;
import ucar.nc2.ft.PointFeatureCollection;
import ucar.nc2.ft.PointFeatureIterator;
import ucar.nc2.ft.StationTimeSeriesFeature;
import ucar.nc2.ft.StationTimeSeriesFeatureCollection;
import ucar.nc2.ft.point.PointIteratorEmpty;
import ucar.nc2.ft.point.StationFeature;
import ucar.nc2.ft.point.StationHelper;
import ucar.nc2.ft.point.StationTimeSeriesCollectionImpl;
import ucar.nc2.ft.point.StationTimeSeriesFeatureImpl;
import ucar.nc2.stream.CdmRemote;
import ucar.nc2.stream.NcStream;
import ucar.nc2.time.CalendarDateRange;
import ucar.nc2.time.CalendarDateUnit;
import ucar.unidata.geoloc.LatLonRect;

/**
 * Connect to remote Station Collection using cdmremote
 *
 * @author caron
 */
public class StationCollectionStream extends StationTimeSeriesCollectionImpl {
  private String uri;
  protected LatLonRect boundingBoxSubset;
  protected CalendarDateRange dateRangeSubset;

  /**
   * Constructor. defer metadata
   *
   * @param uri cdmremote endpoint
   */
  public StationCollectionStream(String uri, CalendarDateUnit timeUnit, String altUnits) {
    super(uri, timeUnit, altUnits);
    this.uri = uri;
  }

  /**
   * initialize the stationHelper.
   */
  @Override
  protected StationHelper createStationHelper() throws IOException {
    // read in all the stations with the "stations" query
    StationHelper stationHelper = new StationHelper();

    try (InputStream in = CdmRemote.sendQuery(null, uri, "req=stations")) {
      PointStream.MessageType mtype = PointStream.readMagic(in);
      if (mtype != PointStream.MessageType.StationList) {
        throw new RuntimeException("Station Request: bad response");
      }

      int len = NcStream.readVInt(in);
      byte[] b = new byte[len];
      NcStream.readFully(in, b);
      PointStreamProto.StationList stationsp = PointStreamProto.StationList.parseFrom(b);
      for (ucar.nc2.ft.point.remote.PointStreamProto.Station sp : stationsp.getStationsList()) {
//        Station s = new StationImpl(sp.getId(), sp.getDesc(), sp.getWmoId(), sp.getLat(), sp.getLon(), sp.getAlt());
        stationHelper.addStation(new StationFeatureStream(null, null));    // LOOK WRONG
      }
      return stationHelper;
    }
  }

  // note this assumes that a PointFeature is-a StationPointFeature
  /* @Override
  public StationFeature getStation(PointFeature feature) throws IOException {
    return (StationFeature) feature; // LOOK probably will fail here
  } */

  // Must override default subsetting implementation for efficiency: eg to make a single call to server

  // StationTimeSeriesFeatureCollection

  @Override
  public StationTimeSeriesFeatureCollection subset(List<StationFeature> stations) throws IOException {
    if (stations == null) return this;
//    List<StationFeature> subset = getStationHelper().getStationFeatures(stations);
    return new Subset(this, null, null); // LOOK WRONG
  }

  @Override
  public StationTimeSeriesFeatureCollection subset(ucar.unidata.geoloc.LatLonRect boundingBox) throws IOException {
    if (boundingBox == null) return this;
    return new Subset(this, boundingBox, null);
  }

  // NestedPointFeatureCollection
  @Override
  public PointFeatureCollection flatten(LatLonRect boundingBox, CalendarDateRange dateRange) throws IOException {
    //boolean restrictedList = false;
    //QueryMaker queryMaker = restrictedList ? new QueryByStationList() : null;
    PointFeatureCollection pfc = new PointCollectionStreamRemote(uri, getTimeUnit(), getAltUnits(), null);
    return pfc.subset(boundingBox, dateRange);
  }

  private class QueryByStationList implements QueryMaker {
    @Override
    public String makeQuery() {
      StringBuilder query = new StringBuilder("stns=");
      for (StationFeature s : getStationHelper().getStationFeatures()) {
        query.append(s.getName());
        query.append(",");
      }
      return PointDatasetRemote.makeQuery(query.toString(), boundingBoxSubset, dateRangeSubset);
    }
  }

  //////////////////////////////////////////////////////////////////////////////

  private static class Subset extends StationCollectionStream {
    StationCollectionStream from;

    Subset(StationCollectionStream from, LatLonRect filter_bb, CalendarDateRange filter_date) throws IOException {
      super(from.uri, from.getTimeUnit(), from.getAltUnits());
      this.from = from;

      if (filter_bb == null)
        this.boundingBoxSubset = from.getBoundingBox();
      else
        this.boundingBoxSubset = (from.getBoundingBox() == null) ? filter_bb : from.getBoundingBox().intersect(filter_bb);

      if (filter_date == null) {
        this.dateRangeSubset = from.dateRangeSubset;
      } else {
        this.dateRangeSubset = (from.dateRangeSubset == null) ? filter_date : from.dateRangeSubset.intersect(filter_date);
      }
    }

    @Override
    protected StationHelper createStationHelper() throws IOException {
      List<ucar.nc2.ft.point.StationFeature> stations = from.getStationHelper().getStationFeatures(boundingBoxSubset);
      StationHelper stationHelper = new StationHelper();
      stationHelper.setStations(stations);
      return stationHelper;
    }

  }

  ///////////////////////////////////////////////////////////////////////////////

  private class StationFeatureStream extends StationTimeSeriesFeatureImpl {
    StationTimeSeriesFeature stnFeature;
    PointIteratorStream riter;

    StationFeatureStream(StationTimeSeriesFeature s, CalendarDateRange dateRange) {
      super(s, StationCollectionStream.this.getTimeUnit(), StationCollectionStream.this.getAltUnits(), -1);
      this.stnFeature = s;
      if (dateRange != null) {
        getInfo();
        info.setCalendarDateRange(dateRange);
      }
    }

    // Must override default subsetting implementation to make a single call to server

    // StationTimeSeriesFeature

    @Override
    public StationTimeSeriesFeature subset(CalendarDateRange dateRange) throws IOException {
      if (dateRange == null) return this;
      return new StationFeatureStream(stnFeature, dateRange);
    }

    @Nonnull
    @Override
    public StructureData getFeatureData() throws IOException {
      return stnFeature.getFeatureData();
    }

    // PointCollection
    @Override
    @Nullable
    public PointFeatureCollection subset(LatLonRect boundingBox, CalendarDateRange dateRange) throws IOException {
      if (boundingBox != null) {
        if (!boundingBox.contains(s.getLatLon())) return null;
        if (dateRange == null) return this;
      }
      return subset(dateRange);
    }

    // an iterator over the observations for this station
    @Override
    public PointFeatureIterator getPointFeatureIterator() throws IOException {
      String query = PointDatasetRemote.makeQuery("stn=" + s.getName(), null, getInfo().getCalendarDateRange(this.getTimeUnit()));

      InputStream in = null;
      try {
        in = CdmRemote.sendQuery(null, uri, query);

        PointStream.MessageType mtype = PointStream.readMagic(in);
        if (mtype == PointStream.MessageType.End) {  // no obs were found
          in.close();
          return new PointIteratorEmpty(); // return empty iterator
        }

        if (mtype != PointStream.MessageType.PointFeatureCollection) {
          throw new RuntimeException("Station Request: bad response = " + mtype);
        }

        int len = NcStream.readVInt(in);
        byte[] b = new byte[len];
        NcStream.readFully(in, b);
        PointStreamProto.PointFeatureCollection pfc = PointStreamProto.PointFeatureCollection.parseFrom(b);

        riter = new PointIteratorStream(StationFeatureStream.this, in, new PointStream.ProtobufPointFeatureMaker(pfc));
        return riter;

      } catch (Throwable t) {
        if (in != null) in.close();
        throw new IOException(t.getMessage(), t);
      }
    }
  }
}


/*
 * Copyright (c) 1998-2018 University Corporation for Atmospheric Research/Unidata
 * See LICENSE for license information.
 */
// $Id: ResultService.java 63 2006-07-12 21:50:51Z edavis $

package thredds.cataloggen.config;

import thredds.catalog.*;

import java.util.Iterator;

/**
 *
Title: Catalog Generator

 *
Description: Tool for generating THREDDS catalogs.

 *
Copyright: Copyright (c) 2001

 *
Company: UCAR/Unidata

 * @author Ethan Davis
 * @version 1.0
 */

public class ResultService extends InvService
{
  //
  private String accessPointHeader = null;

  private boolean isValid = true;
  private StringBuffer log = new StringBuffer();

  /**
   * Constructor
   *
   * @param name
   * @param serviceType
   * @param base
   * @param suffix
   * @param accessPointHeader
   */
  public ResultService( String name, ServiceType serviceType,
                        String base, String suffix,
                        String accessPointHeader)
  {
    super( name, serviceType.toString(), base, suffix, null);
    this.accessPointHeader = accessPointHeader;
  }

  /**
   * Copy constructor.
   * @param service
   */
  protected ResultService( ResultService service)
  {
    this( service, service.getAccessPointHeader());
  }

  public ResultService( InvService service, String accessPointHeader)
  {
    super( service.getName(), service.getServiceType().toString(), service.getBase(),
           service.getSuffix(), service.getDescription());

    for ( Iterator it = service.getProperties().iterator(); it.hasNext(); )
    {
      InvProperty prop = (InvProperty) it.next();
      addProperty( new InvProperty( prop.getName(), prop.getValue())); // LOOK JC changed
    }

    if ( service.getServiceType() == ServiceType.COMPOUND)
    {
      for ( Iterator it = service.getServices().iterator(); it.hasNext(); )
      {
        addService( (InvService) it.next() ); // LOOK JC changed
      }
    }
    this.accessPointHeader = accessPointHeader;

  }

  /**
   * Return a String containing the accessPointHeader attribute text
   *
   * @return String accessPointHeader attribute text
   */
  public String getAccessPointHeader() { return( this.accessPointHeader); }

  public void setAccessPointHeader( String accessPointHeader)
  {
    this.accessPointHeader = accessPointHeader;
  }

  /**
   * Validate this ResultService object. Return true if valid, false if invalid.
   *
   * @param out StringBuffer with validation messages.
   * @return boolean true if valid, false if invalid
   */
  protected boolean validate( StringBuilder out)
  {
    this.isValid = true;

    // If log from construction has content, append to validation output msg.
    if (this.log.length() > 0) {
      out.append( this.log);
    }

    // Check that 'accessPointHeader' attribute is not null.
    if ( this.getAccessPointHeader() == null)
    {
      this.isValid = false;
      out.append( " ** ResultService (1): a null 'accessPointHeader' is invalid.");
    }

    return( this.isValid);
  }

  /** string representation */
  public String toString()
  {
    return( "ResultService[" + super.toString() +
            " accessPointHeader:<" + this.getAccessPointHeader() + ">]");
  }
}