/* * FileStorageModel.java * * Created on March 31, 2004, 9:52 AM */ package org.das2.fsm; import java.awt.EventQueue; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InterruptedIOException; import java.net.URI; import java.net.URISyntaxException; import java.net.UnknownHostException; import java.text.ParseException; import java.util.*; import java.util.logging.Level; import java.util.logging.Logger; import java.util.regex.*; import org.das2.DasApplication; import org.das2.datum.CacheTag; import org.das2.datum.Datum; import org.das2.datum.DatumRange; import org.das2.datum.DatumRangeUtil; import org.das2.datum.TimeParser; import org.das2.datum.TimeUtil.TimeStruct; import org.das2.datum.Units; import org.das2.util.LoggerManager; import org.das2.util.filesystem.FileObject; import org.das2.util.filesystem.FileSystem; import org.das2.util.filesystem.FileSystemUtil; import org.das2.util.filesystem.LocalFileSystem; import org.das2.util.filesystem.SubFileSystem; import org.das2.util.filesystem.WebFileSystem; import org.das2.util.filesystem.ZipFileSystem; import org.das2.util.monitor.NullProgressMonitor; import org.das2.util.monitor.ProgressMonitor; import org.das2.util.monitor.SubTaskMonitor; /** * Represents a method for storing data sets in a set of files by time. The * client provides a regex for the files and how each group of the regex is * interpreted as a time digit. The model can then be used to provide the set * of files that cover a time range, etc. * * This new implementation uses a TimeParser object to more quickly process * file names. * * @author Jeremy */ public class FileStorageModel { private Pattern pattern; private String regex; private Pattern gzpattern; FileStorageModel parent; FileSystem root; TimeParser timeParser; String template; static final Logger logger= LoggerManager.getLogger("das2.system.fsm"); HashMap fileNameMap=null; private boolean allowGz= true; // if true, the getFile can use a .gz version to retrieve a file. List oldVersions= new ArrayList(); /** * Versioning types */ static enum VersioningType { none(null), /** * simple floating point numeric comparisons. */ numeric( new Comparator() { // 4.10 > 4.01 @Override public int compare(Object o1, Object o2) { Double d1= Double.parseDouble((String)o1); Double d2= Double.parseDouble((String)o2); return d1.compareTo(d2); } } ), /** * comparison by lexical sort v2013a>v2012b. */ alphanumeric(new Comparator() { // a001 @Override public int compare(Object o1, Object o2) { return ((String)o1).compareTo((String)o2); } } ), /** * comparison of numbers split by decimal points and dashes, so 1.20 > 1.3. */ numericSplit( new Comparator() { // 4.3.23 // 1.1.3-01 for RBSP (rbspice lev-2 isrhelt) @Override public int compare(Object o1, Object o2) { String[] ss1= o1.toString().split("[\\.-]",-2); String[] ss2= o2.toString().split("[\\.-]",-2); int n= Math.min( ss1.length, ss2.length ); for ( int i=0; i 3.2) } }); Comparator comp; VersioningType( Comparator comp ) { this.comp= comp; } }; VersioningType versioningType; String versionGe= null; // the version must be greater than or equal to this if non-null. String versionLt= null; // the version must be less than this if non-null. /** * return the filesystem used to implement this. * @return filesystem */ public FileSystem getFileSystem() { return this.root; } /** * return a child filesystem, with special code for LocalFileSystems to support * Windows. TODO: look into .zip child. * @param root * @param child * @param monitor * @return the FileSystem * @see https://sourceforge.net/p/autoplot/bugs/2132 * @throws org.das2.util.filesystem.FileSystem.FileSystemOfflineException * @throws UnknownHostException * @throws FileNotFoundException */ public static FileSystem getChildFileSystem( FileSystem root, String child, ProgressMonitor monitor ) throws FileSystem.FileSystemOfflineException, UnknownHostException, FileNotFoundException { FileSystem result; if ( root.getRootURI().getScheme().equals("file") ) { if ( root instanceof LocalFileSystem ) { File localRoot= ((LocalFileSystem)root).getLocalRoot(); result= FileSystem.create( new File( localRoot, child ).toURI(), monitor.getSubtaskMonitor("create") ); } else if ( root instanceof SubFileSystem ) { try { result= root.createFileSystem(child); } catch (URISyntaxException ex) { throw new IllegalArgumentException(ex); } } else if ( root instanceof ZipFileSystem ) { try { result= root.createFileSystem(child); // ((ZipFileSystem)root).createFileSystem(child); } catch (URISyntaxException ex) { throw new IllegalArgumentException(ex); } } else { throw new IllegalArgumentException("not supported, should not happen"); } } else { result= FileSystem.create( root.getRootURI().resolve( child ), monitor.getSubtaskMonitor("create") ); // 3523492: allow the FS type to change; eg to zip. } return result; //fileSystems[i]= root.createFileSystem( names[i] ); //TODO: look into this. // 3523492: allow the FS type to change; eg to zip. } /** * return a random file from the FSM, which can be used to represent a typical file. For * example, we need to look at metadata to see what is available. * @param monitor progress monitor in case a file must be downloaded. * @return a reference to the file within the FileSystem, or null if one is not found. * @throws IOException */ public String getRepresentativeFile( ProgressMonitor monitor ) throws IOException { return getRepresentativeFile( this, monitor, null, null, 0 ); } /** * return a random file from the FSM, which can be used to represent a typical file. For * example, we need to look at metadata to see what is available. * @param monitor progress monitor in case a file must be downloaded. * @param childRegex the parent must contain a file/folder matching childRegex * @return a reference to the file within the FileSystem, or null if one is not found. * @throws IOException */ public String getRepresentativeFile( ProgressMonitor monitor, String childRegex ) throws IOException { return getRepresentativeFile( this, monitor, childRegex, null, 0 ); } /** * Return a random file from the FSM, which can be used to represent a typical file. For * example, we need to look at metadata to see what is available. This is introduced * to support discovery, where we just need one file to * get started. Before, there was code that would list all files, then use * just the first one. This may return a skeleton file, but getFileFor() must * return a result. * This implementation does the same as getNames(), but stops after finding a file. * @param monitor progress monitor in case a file must be downloaded. * @param childRegex the parent must contain a file/folder matching childRegex, or null if there is no child. * @param range hint at the range where we are looking, or null if there is not time range constraint. * @return null if no file is found * @throws java.io.IOException if the file cannot be downloaded. */ public String getRepresentativeFile( ProgressMonitor monitor, String childRegex, DatumRange range ) throws IOException { return getRepresentativeFile( this, monitor,childRegex,range,0 ); } /** * Return a random file from the FSM, which can be used to represent a typical file. For * example, we need to look at metadata of a given file to see what is available inside. * This is introduced to support discovery, where we just need one file to * get started. Before, there was code that would list all files, then use * just the first one. This may return a skeleton file, but getFileFor() must * return a result. * This implementation does the same as getNames(), but stops after finding a file. * @param ths * @param monitor progress monitor in case a file must be downloaded. * @param childRegex the parent must contain a file/folder matching childRegex, or null if there is no child. * @param range hint at the range where we are looking. * @param depth the recursion depth, useful for debugging. * @return null if no file is found * @throws java.io.IOException if the file cannot be downloaded. */ public static String getRepresentativeFile( FileStorageModel ths, ProgressMonitor monitor, String childRegex, DatumRange range, int depth ) throws IOException { logger.log(Level.FINE, "get representative from {0} {1} range: {2}", new Object[]{ths.getFileSystem(), childRegex, range}); //if ( depth==1 ) { //TODO: recusion makes this really hard to debug, and this should be rewritten to remove recursion. // System.err.println("here at depth 1: "+ths.toString() ); //} if ( monitor==null ) monitor= new NullProgressMonitor(); String listRegex; FileSystem[] fileSystems; String[] names; String parentRegex=null; DatumRange range1; // the range from the parent we are looking for. This is to limit searches... if ( EventQueue.isDispatchThread() ) { logger.info("FileSystem use on the GUI event thread will often cause problems."); //new Exception("FileSystem uses event thread stack trace").printStackTrace(); } String result= null; try { if ( ths.parent!=null ) { parentRegex= getParentRegex(ths.regex); String one= getRepresentativeFile( ths.parent, monitor.getSubtaskMonitor("get representative file"),ths.regex.substring(parentRegex.length()+1), range, depth+1 ); if ( one==null ) return null; names= new String[] { one }; //parent.getNamesFor(null); fileSystems= new FileSystem[names.length]; for ( int i=0; i=0; i-- ) { String[] files1= fileSystems[i].listDirectory( "/", listRegex, monitor.getSubtaskMonitor("create") ); //to avoid the case where the latest folder is empty, check n-1 down to 0, then n. //int n= files1.length; //String[] resort= new String[n]; //for ( int j=0; j=0 && result==null ) { String ff= names[i].equals("") ? files1[ j ] : names[i]+"/"+files1[ j ]; if ( ff.endsWith("/") ) ff=ff.substring(0,ff.length()-1); try { HashMap extra= new HashMap(); DatumRange tr= getDatumRangeFor( ths, ff, extra ); boolean versionOk= true; if ( ths.versionGe!=null && ths.versioningType.comp.compare( extra.get("v"), ths.versionGe )<0 ) versionOk=false; if ( ths.versionLt!=null && ths.versioningType.comp.compare( extra.get("v"), ths.versionLt )>=0 ) versionOk=false; if ( versionOk && ths.timeParser.getValidRange().contains( tr ) && ( range==null || range.intersects(tr) ) ) { if ( childRegex!=null ) { String[] kids= fileSystems[i].listDirectory( files1[ j ],childRegex, monitor.getSubtaskMonitor("list directory") ); if ( kids.length>0 ) { result= ff; } } else { result= ff; } } } catch ( IllegalArgumentException ex ) { logger.log( Level.FINER, null, ex ); } if ( result==null ) j--; } } if ( ths.allowGz ) { if ( result==null) { for ( int i=fileSystems.length-1; result==null && i>=0; i-- ) { String[] files1= fileSystems[i].listDirectory( "/", listRegex + ".gz" ); if ( files1.length>0 ) { int last= files1.length-1; String ff= names[i].equals("") ? files1[ last ] : names[i]+"/"+files1[ last ]; if ( ff.endsWith("/") ) ff=ff.substring(0,ff.length()-1); result= ff.substring( 0,ff.length()-3 ); } } } } if ( result==null ) { if ( ths.parent==null ) { return null; } else { // fall back to old code that would list everything. logger.fine("fall back to old code that would list everything"); range1= ths.parent.getRangeFor(names[0]); range1= range1.previous(); if ( range!=null && !range.intersects(range1) ) { return null; } String one= getRepresentativeFile( ths.parent, monitor.getSubtaskMonitor("getRepresentativeFile"), ths.regex.substring(parentRegex.length()+1), range1, depth+1 ); if ( one==null ) return null; names= new String[] { one }; //parent.getNamesFor(null); fileSystems= new FileSystem[names.length]; for ( int i=0; i extra ) { try { extra.clear(); if ( pattern.matcher(filename).matches() ) { timeParser.parse( filename, extra ); return timeParser.getTimeRange(); } else { if ( gzpattern!=null && gzpattern.matcher(filename).matches() ) { timeParser.parse( filename.substring(0,filename.length()-3), extra ); return timeParser.getTimeRange(); } else { throw new IllegalArgumentException( "file name \""+filename+"\" doesn't match model specification ("+template+")"); } } } catch ( ParseException | NumberFormatException e ) { IllegalArgumentException e2=new IllegalArgumentException( "file name \""+filename+"\" doesn't match model specification ("+template+"), parse error in field",e); throw e2; } } private static DatumRange getDatumRangeFor( FileStorageModel ths, String filename, Map extra ) { try { extra.clear(); if ( ths.pattern.matcher(filename).matches() ) { ths.timeParser.parse( filename, extra ); return ths.timeParser.getTimeRange(); } else { if ( ths.gzpattern!=null && ths.gzpattern.matcher(filename).matches() ) { ths.timeParser.parse( filename.substring(0,filename.length()-3), extra ); return ths.timeParser.getTimeRange(); } else { throw new IllegalArgumentException( "file name \""+filename+"\" doesn't match model specification ("+ths.template+")"); } } } catch ( ParseException | NumberFormatException e ) { IllegalArgumentException e2=new IllegalArgumentException( "file name \""+filename+"\" doesn't match model specification ("+ths.template+"), parse error in field",e); throw e2; } } /** * return a filename that would intersect the range. Note this file * may not actually exist. This may be used to quantize the range. * The template may not contain versions. * @param start * @param end * @return */ public String getFilenameFor( Datum start, Datum end ) { return timeParser.format( start, end ); } /** * generate the names of the files that would cover this range. This was * taken from Autoplot's org.virbo.jythonsupport.Util. * TODO: versioning, etc. * @param range the time range to cover. * @return the string names, each in the context of the filesystem. */ public String[] generateNamesFor( DatumRange range ) { String sstart; TimeParser tp= timeParser; try { sstart= tp.format( range.min(), null ); } catch ( Exception ex ) { // orbit files have limited range DatumRange dr= tp.getValidRange(); DatumRange dd= DatumRangeUtil.sloppyIntersection(range, dr); if ( dd.width().value()==0 ) { return new String[0]; // no intersection } sstart= tp.format( dd.min(), null ); } try { tp.parse(sstart); } catch ( ParseException ex ) { throw new RuntimeException(ex); } DatumRange curr= tp.getTimeRange(); int countLimit= 1000000; int approxCount= (int)( 1.01 * range.width().divide(curr.width()).value() ); // extra 1% for good measure. if ( approxCount>countLimit*1.03 ) { throw new IllegalArgumentException("too many intervals would be created, this is limited to about 1000000 intervals."); } List result= new ArrayList<>( approxCount ); if ( !range.intersects(curr) ) { // Sebastian has a strange case that failed, see curr= curr.next(); } while ( range.intersects(curr) ) { String scurr= tp.format( curr.min(), curr.max() ); result.add( scurr ); DatumRange oldCurr= curr; curr= curr.next(); if ( oldCurr.equals(curr) ) { // orbits return next() that is this at the ends. break; } } return result.toArray( new String[result.size()] ); } /** * return the timerange that contains the given timerange and * exactly contains a set of granules. * * This needs to be synchronized because the timeParser. * @param timeRange arbitrary time range * @return list of file timeranges covering file input timeRange. */ public synchronized DatumRange quantize(DatumRange timeRange) { try { String tf1= timeParser.format( timeRange.min(), timeRange.min() ); String tf2= timeParser.format( timeRange.max(), timeRange.max() ); DatumRange dr1,dr2; try { dr1= timeParser.parse(tf1).getTimeRange(); dr2= timeParser.parse(tf2).getTimeRange(); // if ( true ) { // TimeParser tp1= timeParser.parse(tf1); // logger.log( Level.WARNING, "tp1 start {0}", tp1.getTime(Units.us2000) ); // logger.log( Level.WARNING, "tp1 end {0}", tp1.getEndTime(Units.us2000) ); // logger.log( Level.WARNING, "tp1 tr {0}", tp1.getTimeRange() ); // TimeParser tp2= timeParser.parse(tf2); // logger.log( Level.WARNING, "tp2 start {0}", tp2.getTime(Units.us2000) ); // logger.log( Level.WARNING, "tp2 end {0}", tp2.getEndTime(Units.us2000) ); // logger.log( Level.WARNING, "tp2 tr {0}", tp2.getTimeRange() ); // } } catch ( IllegalArgumentException ex ) { logger.log(Level.WARNING, "Strange bug shown in test033: {2}\n>>{0}<<\n>>{1}<<", new Object[]{tf1, tf2,this.timeParser}); TimeParser tp1= timeParser.parse(tf1); logger.log( Level.WARNING, "tp1 start {0}", tp1.getTime(Units.us2000) ); logger.log( Level.WARNING, "tp1 end {0}", tp1.getEndTime(Units.us2000) ); logger.log( Level.WARNING, "tp1 tr {0}", tp1.getTimeRange() ); TimeParser tp2= timeParser.parse(tf2); logger.log( Level.WARNING, "tp2 start {0}", tp2.getTime(Units.us2000) ); logger.log( Level.WARNING, "tp2 end {0}", tp2.getEndTime(Units.us2000) ); logger.log( Level.WARNING, "tp2 tr {0}", tp2.getTimeRange() ); throw ex; } if ( dr2.min().equals(timeRange.max() ) ) { return DatumRangeUtil.union( dr1, dr2.min() ); } else { return DatumRangeUtil.union( dr1, dr2 ); } } catch (ParseException ex) { throw new RuntimeException("this shouldn't happen"); } } /** * return the names in the range, or all names if the range is null. * @param targetRange range limit, or null. * @return array of names within the system. * @throws java.io.IOException */ public String[] getNamesFor( final DatumRange targetRange ) throws IOException { return getNamesFor( targetRange, false, new NullProgressMonitor() ); } /** * return the names in the range, or all available names if the range is null. * This will list directories. * @param targetRange range limit, or null. * @param monitor * @return array of names within the system. * @throws java.io.IOException */ public String[] getNamesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { return getNamesFor( targetRange, false, monitor ); } /** * return the names in the range, minding version numbers, or all available * names if the range is null. This will list directories. * @param targetRange range limit, or null. * @param monitor * @return array of names within the system. * @throws java.io.IOException */ public String[] getBestNamesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { return getNamesFor( targetRange, true, monitor ); } /** * return the names in the range, minding version numbers, or all available * names if the range is null. This will list directories. * @param targetRange range limit, or null. * @return array of names within the system. * @throws java.io.IOException */ public String[] getBestNamesFor( final DatumRange targetRange ) throws IOException { return getNamesFor( targetRange, true, new NullProgressMonitor() ); } /** * return the names in the range, maybe with versioning. This will * list directories. * @param targetRange range limit, or null if no constraint used here. * @param versioning true means check versions. * @param monitor progress monitor (or null) * @return array of names within the system. * @throws IOException * @see generateNamesFor */ private String[] getNamesFor( final DatumRange targetRange, boolean versioning, ProgressMonitor monitor ) throws IOException { logger.log( Level.FINE, "getNamesFor {0}", this.root); if ( monitor==null ) monitor= new NullProgressMonitor(); String listRegex; FileSystem[] fileSystems; String[] names; oldVersions.clear(); if ( parent!=null ) { names= parent.getNamesFor(targetRange,versioning,new NullProgressMonitor()); // note recursive call logger.log(Level.FINE, "parent {0} yields: {1}", new Object[]{parent.toString(), names.length}); fileSystems= new FileSystem[names.length]; for ( int i=0; i list= new ArrayList(); List versionList= new ArrayList(); List rangeList= new ArrayList(); monitor.setTaskSize( fileSystems.length*10 ); monitor.started(); Map extra= new HashMap(); for ( int i=0; i{1}", new Object[]{theListRegex, files1.length}); Arrays.sort(files1); for ( int j=0; j deleteRemote= new ArrayList(); List remoteFiles= Arrays.asList( files1 ); for ( String s: files2 ) { if ( remoteFiles.indexOf(s)==-1 ) { deleteRemote.add(s); } } logger.log(Level.FINE, "local files that do not exist on remote: {0}", deleteRemote); oldVersions.addAll(deleteRemote); } } } if ( versioning && versioningType!=VersioningType.none) { Comparator comp= versioningType.comp; Map bestVersions= new HashMap(); Map bestFiles= new HashMap(); for ( int j=0; j=0 ) && ( versionLt==null || comp.compare( thss, versionLt )<0 ) ){ bestVersions.put( key, thss ); bestFiles.put( key,ff ); } } catch ( Exception ex ) { logger.log( Level.WARNING, ex.getMessage(), ex ); // doesn't match if comparator (e.g. version isn't a decimal number) } } else { try { if ( ( ( versionGe==null || comp.compare( thss, versionGe )>=0 ) ) && ( versionLt==null || comp.compare( thss, versionLt )<0 ) && comp.compare( thss, best ) > 0 ) { bestVersions.put( key,thss ); String rm= bestFiles.put( key,ff ); if ( !oldVersions.contains(rm) ) { oldVersions.add(rm); } } } catch ( Exception ex ) { logger.log( Level.WARNING, ex.getMessage(), ex ); // doesn't match } } } list= Arrays.asList( bestFiles.values().toArray( new String[ bestFiles.size()] ) ); } Collections.sort( list, new Comparator() { @Override public int compare( Object o1, Object o2 ) { DatumRange dr1= getRangeFor( (String)o1 ); DatumRange dr2= getRangeFor( (String)o2 ); return dr1.compareTo( dr2 ); } } ); logger.log( Level.FINE, "getNamesFor {0} -> {1}", new Object[] { this.root, list.size() } ); monitor.finished(); return (String[])list.toArray(new String[list.size()]); } public static CacheTag getCacheTagFor( FileStorageModel fsm, DatumRange range, String[] names ) { Datum min= range.min(); Datum max= range.max(); for (String name : names) { DatumRange r = fsm.getRangeFor(name); min= min.gt(range.min()) ? r.min() : min; max= max.lt(range.max()) ? r.max() : max; } return new CacheTag( min, max, null ); } public static CacheTag getCacheTagFor( FileStorageModel fsm, DatumRange range, File[] files ) { String[] names= new String[files.length]; for ( int i=0; i hh= new HashMap(); getDatumRangeFor( name, hh ); if ( hh.containsKey(field) ) { return hh.get(field); } else { throw new IllegalArgumentException("field is not in template: "+field); } } /** * return true if the file came (or could come) from this FileStorageModel. * @param file the file * @return true if the file came (or could come) from this FileStorageModel. */ public boolean containsFile( File file ) { if ( !fileNameMap.containsKey(file) ) { return false; } else { String name= getNameFor( file ); return containsName(name); } } /** * return true if the name came (or could come) from this FileStorageModel. * @param name the name within the filesystem. * @return true if the name came (or could come) from this FileStorageModel. */ public boolean containsName( String name ) { Matcher m= pattern.matcher( name ); return m.matches(); } /** * Provides a way to recover the model name of a file. The returned File from getFilesFor can be anywhere, * so it would be good to provide a way to get it back into a FSM name. For example, a filesystem * might download the remote file to a cache directory, which is the File that is provided to the * client, sometimes the client will need to recover the name of the corresponding FileObject, so * this maps the File back to the name. * * @param file the file * @return the canonical name of the file. */ public String getNameFor( File file ) { String result= (String)fileNameMap.get(file); if ( result==null ) { throw new IllegalArgumentException( "File didn't come from this FileStorageModel" ); } else { return result; } } /** * return the root of the filesystem as a string. * @return the root of the filesystem as a string. */ public String getRoot() { return root.getRootURI().toString(); } /** * check to see if "NAME.gz" exists * @param name name of uncompressed file * @param mon progress monitor (or null) * @return null or the uncompressed file. * @throws IOException */ private File maybeGetGzFile( String name, ProgressMonitor mon) throws IOException { File f0 = null; if ( mon==null ) mon= new NullProgressMonitor(); FileObject oz = root.getFileObject(name + ".gz"); if (oz.exists()) { File fz = oz.getFile(mon); String sfz = fz.getPath().substring(0, fz.getPath().length() - 3); f0 = new File(sfz); FileSystemUtil.gunzip(fz, f0); if ( !f0.setLastModified(fz.lastModified()) ) { throw new IllegalArgumentException("failed to set last modified"); } } return f0; } /** * download the file for the given name within the filesystem. * @param name the name within the filesystem. * @return null or a local file which can be opened. * @throws IOException */ public File getFileFor( String name ) throws IOException { File[] ff= getFilesFor( new String[] { name }, new NullProgressMonitor() ); if ( ff.length>0 ) { return ff[0]; } else { return null; } } /** * download the file for the given name within the filesystem. * @param name the name within the filesystem. * @param monitor monitor for the download. * @return null or a local file which can be opened. * @throws IOException */ public File getFileFor( String name, ProgressMonitor monitor ) throws IOException { File[] ff= getFilesFor( new String[] { name }, monitor ); if ( ff.length>0 ) { return ff[0]; } else { return null; } } /** * download the files for each of the given names within the filesystem. * @param names array of names within the filesystem * @param monitor monitor for the downloads (or null). * @return local files that can be opened. * @throws java.io.IOException during the transfer */ public File[] getFilesFor( String [] names, ProgressMonitor monitor ) throws IOException { if ( monitor==null ) monitor= new NullProgressMonitor(); File[] files= new File[names.length]; if ( fileNameMap==null ) fileNameMap= new HashMap(); int numwarn= 0; if ( names.length>0 ) monitor.setTaskSize( names.length * 10 ); monitor.started(); for ( int i=0; i result= new ArrayList(files.length); int i=0; for (File file : files) { if (file != null) { result.add(file); i++; } } return result.toArray( new File[i] ); } /** * Download the files within the range. * This might catch a bad link where getNamesFor does not. * @param targetRange range limit, or null if no constraint used here. * @param monitor the monitor * @return a list of files that can be opened. * @throws java.io.IOException */ public File[] getFilesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { String[] names= getNamesFor( targetRange ); File[] ff= getFilesFor( names, monitor ); return ff; } /** * Get the files for the range, using versioning info ($v,etc). * @param targetRange range limit, or null if no constraint used here. * @param monitor the monitor * @return a list of files that can be opened. * @throws IOException */ public File[] getBestFilesFor( final DatumRange targetRange, ProgressMonitor monitor ) throws IOException { if ( monitor==null ) monitor= new NullProgressMonitor(); String[] names= getNamesFor( targetRange, true, monitor.getSubtaskMonitor("get names") ); File[] files= new File[names.length]; if ( fileNameMap==null ) fileNameMap= new HashMap(); if ( names.length>0 ) monitor.setTaskSize( names.length * 10 ); monitor.started(); for ( int i=0; i1 ) { dirRegex= new StringBuilder( s[0] ); for ( int i=1; i-1 && result.indexOf("%")>i ) { System.err.println("each folder of template must have fields marked by $ or %: "+ result.substring(0,i) ); } if ( result.startsWith("/") ) { result= result.substring(1); } return result; } /** * hide the contents of parameters as in
     *   product_%(o,id=ftp://stevens.lanl.gov/pub/projects/rbsp/autoplot/orbits/rbspa_pp).png -->
     *   product_%(______________________________________________________________________).png
     * 
* @param template * @return */ private static String hideParams( String template ) { StringBuilder result= new StringBuilder(); boolean withinArg= false; for ( int i=0; i *
http://autoplot.org/data/
     *C1_CP_EDI_EGD__$Y$m$d_V$v.cef
and * http://emfisis.physics.uiowa.edu/Flight/RBSP-A/Quick-Look/$Y/$m/$d/rbsp-a_magnetometer_4sec-gsm_emfisis-Quick-Look_$Y$m$d_v$(v,sep).cdf:
*
http://emfisis.physics.uiowa.edu/Flight/RBSP-A/Quick-Look/
     *$Y/$m/$d/rbsp-a_magnetometer_4sec-gsm_emfisis-Quick-Look_$Y$m$d_v$(v,sep).cdf
*
* *

* This new version uses regexs and is more complete than versions found in Autoplot, and they should * eventually use this instead. Note the Autoplot one returns the index of the last /, whereas this * returns that index plus one. *

* *

Taken from Autoplot's AggregatingDataSourceFactory, where Autoplot just has a URI and needs to get a file list. * See also org/autoplot/pngwalk/WalkUtil.java splitIndex, which also allows wildcards like *.

* * Note, ages ago %Y was valid as well as $Y. This is no longer supported, since it is useful * to have Python templates working alongside URI_Templates. * * @param surl a string like http://autoplot.org/data/C1_CP_EDI_EGD__$Y$m$d_V$v.cef * @return an integer indicating the split index, so that surl.substring(0,i) includes the slash. */ public static int splitIndex(String surl) { String regex= "([\\$][yYmdjxv\\(\\{])"; Matcher m= Pattern.compile(regex).matcher(surl); if ( m.find() ) { int i= m.start(); i = surl.lastIndexOf('/', i); return i+1; } else { return -1; } } /** * creates a FileStorageModel for the given template, which uses: *
%Y-%m-%dT%H:%M:%S.%{milli}Z";
     *    %Y  4-digit year
     *    %m  2-digit month
     *    %d  2-digit day of month
     *    %j  3-digit day of year
     *    %H  2-digit Hour
     *    %M  2-digit Minute
     *    %S  2-digit second
     *    %v  best version by number  Also %(v,sep) for 4.3.2  or %(v,alpha)
     *    %{milli}  3-digit milliseconds 
* product_$(o,id=ftp://stevens.lanl.gov/pub/projects/rbsp/autoplot/orbits/rbspa_pp).png * @param root FileSystem source of the files. * @param template describes how filenames are constructed. This is converted to a regular expression and may contain regex elements without groups. The * string may contain $ instead of percents as long as there are no percents in the string. * * @return a newly-created FileStorageModel. */ public static FileStorageModel create( FileSystem root, String template ) { template= makeCanonical(template); String templatebr= hideParams( template ); int i= templatebr.lastIndexOf("/"); if ( template.contains("%") && !template.contains("$") ) { //TODO: makeCanonical should do this. template= template.replaceAll("\\%", "\\$"); templatebr= templatebr.replaceAll("\\%", "\\$"); } int i2= templatebr.lastIndexOf("$",i); if ( i2 != -1 ) { String parentTemplate= template.substring(0,i); FileStorageModel parentFSM= FileStorageModel.create( root, parentTemplate ); return new FileStorageModel( parentFSM, root, template ); } else { return new FileStorageModel( null, root, template ); } } /** * creates a FileStorageModel for the given template, but with a custom FieldHandler and * field. * * @param root FileSystem source of the files. * @param template describes how filenames are constructed. This is converted to a regular expression and may contain regex elements without groups. The * string may contain $ instead of percents as long as there are no percents in the string. * @param fieldName custom field name * @param fieldHandler TimeParser.FieldHandler to call with the field contents. * @return a newly-created FileStorageModel. */ public static FileStorageModel create( FileSystem root, String template, String fieldName, TimeParser.FieldHandler fieldHandler ) { template= makeCanonical(template); String templatebr= hideParams( template ); int i= templatebr.lastIndexOf("/"); if ( template.contains("%") && !template.contains("$") ) { template= template.replaceAll("\\%", "\\$"); templatebr= templatebr.replaceAll("\\%", "\\$"); } int i2= templatebr.lastIndexOf("$",i); if ( i2 != -1 ) { String parentTemplate= template.substring(0,i); FileStorageModel parentFSM= FileStorageModel.create( root, parentTemplate, fieldName, fieldHandler ); return new FileStorageModel( parentFSM, root, template, fieldName, fieldHandler ); } else { return new FileStorageModel( null, root, template, fieldName, fieldHandler ); } } private FileStorageModel( FileStorageModel parent, FileSystem root, String template, String fieldName, TimeParser.FieldHandler fieldHandler, Object ... moreHandler ) { this.root= root; this.parent= parent; if ( template.startsWith("/") ) { // clean up double slashes immediately. (/home/jbf//data/) template= template.substring(1); } while ( template.contains("//") ) { template= template.replaceAll("\\/\\/", "/"); } this.template= template.replaceAll("\\+", "\\\\+"); String f="v"; versioningType= VersioningType.none; TimeParser.FieldHandler vh= new TimeParser.FieldHandler() { @Override public String configure( Map args ) { String sep= args.get( "sep" ); if ( sep==null && args.containsKey("dotnotation")) { sep= "T"; } String alpha= args.get( "alpha" ); if ( alpha==null && args.containsKey("alphanumeric") ) { alpha="T"; } String type= args.get("type"); if ( type!=null ) { if ( type.equals("sep") || type.equals("dotnotation") ) { sep= "T"; } else if (type.equals("alpha") || type.equals("alphanumeric") ) { alpha="T"; } } if ( args.get("gt")!=null ) { throw new IllegalArgumentException("gt specified but not supported: must be ge or lt"); } if ( args.get("le")!=null ) { throw new IllegalArgumentException("le specified but not supported: must be ge or lt"); } String ge= args.get("ge"); if ( ge!=null ) { versionGe= ge; } String lt= args.get("lt"); if ( lt!=null ) { versionLt= lt; } if ( alpha!=null ) { if ( sep!=null ) { return "alpha with split not supported"; } else { versioningType= VersioningType.alphanumeric; } } else { if ( sep!=null ) { versioningType= VersioningType.numericSplit; } else { versioningType= VersioningType.numeric; } } return null; } @Override public void parse( String fieldContent, TimeStruct startTime, TimeStruct timeWidth, Map extra ) { String v= extra.get("v"); if ( v!=null ) { versioningType= VersioningType.numericSplit; fieldContent= v+"."+fieldContent; // Support $v.$v.$v } extra.put( "v", fieldContent ); } @Override public String getRegex() { return ".*"; } @Override public String format( TimeStruct startTime, TimeStruct timeWidth, int length, Map extra ) { return extra.get("v"); //TODO: length } }; if ( fieldName==null ) { this.timeParser= TimeParser.create( template, f, vh ); } else { if ( moreHandler==null || moreHandler.length==0 ) { //TODO: check if it can ever be null. this.timeParser= TimeParser.create( template, fieldName, fieldHandler, f, vh ); } else { this.timeParser= TimeParser.create( template, fieldName, fieldHandler, f, vh, moreHandler ); } } //if ( this.timeParser.isStartTimeOnly() ) { // this.startTimeOnly= true; //} this.regex= timeParser.getRegex(); this.pattern= Pattern.compile(regex); if ( template.endsWith(".gz") ) { allowGz= false; // turn off automatic uncompressing GZ files. } if ( allowGz ) { this.gzpattern= Pattern.compile(regex+"\\.gz"); } } // /** // * The filename time only contains the start time of the interval, the end of the interval // * is only bounded by the next file. // */ // protected boolean startTimeOnly = false; // // /** // * limit on the length of files with startTimeOnly set. // * e.g. $Y$m$d_$(H,startTimeOnly)$M means that the files should be much less that one hour long // * // */ // protected Datum implicitTimeDelta= null; private FileStorageModel( FileStorageModel parent, FileSystem root, String template ) { this( parent, root, template, null, null ); } @Override public String toString() { return String.valueOf(root) + template; } }