/* * AbstractDataSource.java * * Created on April 1, 2007, 7:12 AM * * To change this template, choose Tools | Template Manager * and open the template in the editor. */ package org.autoplot.datasource; import org.das2.util.monitor.ProgressMonitor; import org.das2.util.monitor.NullProgressMonitor; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; import java.text.ParseException; import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; import static org.autoplot.datasource.DataSetURI.fromUri; import static org.autoplot.datasource.DataSetURI.getFile; import org.das2.qds.buffer.BufferDataSet; import org.das2.dataset.NoDataInIntervalException; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.Units; import org.das2.qds.DataSetOps; import org.das2.qds.DataSetUtil; import org.das2.qds.MutablePropertyDataSet; import org.das2.qds.QDataSet; import org.das2.qds.SemanticOps; import org.autoplot.datasource.capability.Updating; import org.das2.qds.ops.CoerceUtil; import org.das2.qds.ops.Ops; /** * Base class for file-based DataSources that keeps track of the uri, makes * the parameters available, manages capabilities and has do-nothing * implementations for rarely-used methods of DataSource. * * Also this provides the filePollUpdating parameter and Updating capability. * * @author jbf */ public abstract class AbstractDataSource implements DataSource { protected static final Logger logger= Logger.getLogger("apdss"); protected URI uri; /** * available to subclasses for convenience. This is the name of the file, * without the parameters. */ protected URI resourceURI; public AbstractDataSource(java.net.URI uri) { this.uri = uri; String s = DataSetURI.fromUri(uri); if ( !s.startsWith("vap") ) { logger.fine( "uri didn't start with vap!" ); } URISplit split = URISplit.parse(s); params = URISplit.parseParams(split.params); String f= split.file; if ( split.scheme!=null ) { try { resourceURI = DataSetURI.toUri(f); } catch (Exception e) { //URI syntax exception logger.fine(e.toString()); // InlineDataSource is subclass, need to fix this... } } } /** * returns the uri's canonical extension for convenience. * The extension does contain the initial period and is folded to lower case. * Returns an empty string if no extension is found. * * Note that this is not necessarily the extension associated with the DataSource. For example, * ImageDataSource has a canonical extension of ".jpg", but for a png file this will return .png. * * @return lower-case extension with a period, or empty string. */ protected String getExt(URL url) { try { return getExt(url.toURI()); } catch (URISyntaxException e) { logger.fine("Failed to convert URL to URI."); return ""; } /*String s = url.getFile(); int i = s.lastIndexOf("."); // URI okay if (i == -1) { return ""; } else { return s.substring(i).toLowerCase(); }*/ } protected String getExt(URI uri) { String s = uri.getPath(); int i = s.lastIndexOf('.'); if (i == -1) { return ""; } else { return s.substring(i).toLowerCase(); } } /** * available to subclasses for convenience. */ protected Map params; @Override public abstract QDataSet getDataSet(ProgressMonitor mon) throws Exception; @Override public boolean asynchronousLoad() { return true; } @Override public String toString() { return DataSetURI.fromUri(uri); } @Override public String getURI() { return DataSetURI.fromUri(uri); } FilePollUpdating pollingUpdater; /** * get an input stream from the data source. * @param mon * @return * @throws IOException */ protected InputStream getInputStream( ProgressMonitor mon ) throws IOException { if ( uri.getScheme().equals("jar") ) { return uri.toURL().openStream(); //TODO: experiment, make this production-quality } else { return new FileInputStream( getFile(mon) ); } } /** * make the remote file available. * @param mon * @return * @throws java.io.IOException */ protected File getFile(ProgressMonitor mon) throws IOException { if ( resourceURI==null || resourceURI.toString().equals("") ) { throw new IllegalArgumentException("expected file but didn't find one, check URI for question mark"); } return getFile( resourceURI, mon ); } // Practically identical to the URL version below... protected File getFile(URI uri, ProgressMonitor mon) throws IOException { //DataSetURI.getFile(uri, mon); String suri= fromUri( uri ); File f = DataSetURI.getFile(suri,"T".equals(params.get("allowHtml")),mon); if (params.containsKey("filePollUpdates")) { int poll= (int)(Double.parseDouble(params.get("filePollUpdates")) ); pollingUpdater= new FilePollUpdating(uri,poll); pollingUpdater.startPolling(); capabilities.put(Updating.class,pollingUpdater ); } return f; } /** * make the remote file available. If the parameter "filePollUpdates" is set to * a float, a thread will be started to monitor the local file for updates. * This is done by monitoring for file length and modification time changes. * @param url * @param mon * @return * @throws java.io.IOException */ protected File getFile( URL url, ProgressMonitor mon ) throws IOException { File f = DataSetURI.getFile( url, mon ); if (params.containsKey("filePollUpdates")) { try { int poll= (int)(Double.parseDouble(params.get("filePollUpdates")) ); pollingUpdater= new FilePollUpdating(url.toURI(),poll); pollingUpdater.startPolling(); capabilities.put(Updating.class,pollingUpdater ); } catch (URISyntaxException ex) { Logger.getLogger(AbstractDataSource.class.getName()).log(Level.SEVERE, null, ex); } } return f; } /** * get the file, allowing content to be html. * @param url * @param mon * @return * @throws IOException * @see #getFile(java.net.URI, org.das2.util.monitor.ProgressMonitor) which checks for "allowHtml=T" */ protected File getHtmlFile( URL url, ProgressMonitor mon ) throws IOException { File f = DataSetURI.getHtmlFile( url, mon ); if (params.containsKey("filePollUpdates")) { try { int poll= (int)(Double.parseDouble(params.get("filePollUpdates")) ); pollingUpdater= new FilePollUpdating(url.toURI(),poll); pollingUpdater.startPolling(); capabilities.put(Updating.class,pollingUpdater ); } catch (URISyntaxException ex) { Logger.getLogger(AbstractDataSource.class.getName()).log(Level.SEVERE, null, ex); } } return f; } /** * return the parameters from the URI (a copy). * @return the parameters from the URI. */ protected Map getParams() { logger.log(Level.FINER, "getParams()"); return new LinkedHashMap(params); } /** * return the named parameter, or the default. * Note arg_0, arg_1, etc are for unnamed positional parameters. It's recommended * that there be only one positional parameter. * @param name the parameter name * @param dflt the default, which is returned when the parameter is not found. * @return the parameter value, or dflt when the parameter is not found. */ protected final String getParam( String name, String dflt ) { logger.log(Level.FINER, "getParam(\"{0}\")", name); String result= params.get(name); if (result!=null ) { return result; } else { return dflt; } } /** * abstract class version returns an empty tree. Override this method * to provide metadata. * @param mon progress monitor * @return * @throws java.lang.Exception */ @Override public Map getMetadata(ProgressMonitor mon) throws Exception { return new HashMap<>(); } /** * return a MetadataModel object that can make the metadata canonical. * For example, ISTPMetadataModel interprets the metadata returned from CDF files, * but this same model can be used with HDF files. This returns a null model * that does no interpretation, and some data sources will override this. * @return */ @Override public MetadataModel getMetadataModel() { return MetadataModel.createNullModel(); } /** * return metadata in canonical form using the metadata model. If there * are no properties or a null model, then an empty map is returned. * Note, getMetadataModel should return non-null, and getMetadata should return non-null, * but this guards against the mistake. * @return */ @Override public Map getProperties() { try { Map meta = getMetadata(new NullProgressMonitor()); if ( meta==null || getMetadataModel()==null ) { logger.log( Level.FINE, "handling case where metadata or metadataModel is null: {0}, but this should be fixed.", this); meta= getMetadata(new NullProgressMonitor()); //return Collections.emptyMap(); } if ( meta==null || getMetadataModel()==null ) { return Collections.emptyMap(); } return getMetadataModel().properties(meta); } catch (Exception e) { logger.log( Level.SEVERE, "exception in getProperties", e ); return Collections.singletonMap("Exception", (Object) e); } } /** * * @param result the dataset * @param parm the param used to filter. * @param op one of gt,lt,eq,ne,within * @param d rank 0 value or rank 1 range for the "within" operator. * @return the dataset with the filter applied. (Note this may or may not be the same object.) */ private MutablePropertyDataSet applyFilter( MutablePropertyDataSet result, QDataSet parm, String op, QDataSet d ) throws NoDataInIntervalException { QDataSet r; if ( parm.rank()>1 && parm.rank()1 ) { switch (op) { case "gt": r= Ops.where( Ops.le( parm,d ) ); break; case "lt": r= Ops.where( Ops.ge( parm,d ) ); break; case "eq": r= Ops.where( Ops.ne( parm,d ) ); break; case "ne": r= Ops.where( Ops.eq( parm,d ) ); break; case "within": r= Ops.where( Ops.without( parm,d ) ); break; default: throw new IllegalArgumentException("where can only contain .eq, .ne, .gt, or .lt"); } double fill= Double.NaN; result= BufferDataSet.maybeCopy(result); if ( parm.rank()==2 && result.rank()==2 ) { for ( int jj=0; jj2"); } result= applyFilter(result, parm, op, d ); } return result; } private HashMap capabilities = new HashMap(); /** * attempt to get a capability. null will be returned if the * capability doesn't exist. * @param the capability * @param clazz the capability class. * @return null or an implementation of a capability. */ @Override public T getCapability(Class clazz) { return (T) capabilities.get(clazz); } /** * attach a capability * @param the capability * @param clazz the capability class. * @param o an implementation. * @deprecated use addCapability */ public void addCability(Class clazz, T o) { capabilities.put(clazz, o); } /** * attach a capability * @param the capability * @param clazz the capability class. * @param o an implementation. */ public void addCapability( Class clazz, T o) { capabilities.put(clazz, o); } }