Index: applications/editors/josm/plugins/opendata/build.xml
===================================================================
--- applications/editors/josm/plugins/opendata/build.xml	(revision 30309)
+++ applications/editors/josm/plugins/opendata/build.xml	(revision 30310)
@@ -1,3 +1,3 @@
-﻿<?xml version="1.0" encoding="utf-8"?>
+<?xml version="1.0" encoding="utf-8"?>
 <project name="opendata" default="dist" basedir=".">
     <property name="plugin.main.version" value="6412"/>
Index: applications/editors/josm/plugins/opendata/src/org/geotools/data/shapefile/TabFiles.java
===================================================================
--- applications/editors/josm/plugins/opendata/src/org/geotools/data/shapefile/TabFiles.java	(revision 30309)
+++ 	(revision )
@@ -1,953 +1,0 @@
-//    JOSM opendata plugin.
-//    Copyright (C) 2011-2012 Don-vip
-//
-//    This program is free software: you can redistribute it and/or modify
-//    it under the terms of the GNU General Public License as published by
-//    the Free Software Foundation, either version 3 of the License, or
-//    (at your option) any later version.
-//
-//    This program is distributed in the hope that it will be useful,
-//    but WITHOUT ANY WARRANTY; without even the implied warranty of
-//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//    GNU General Public License for more details.
-//
-//    You should have received a copy of the GNU General Public License
-//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
-package org.geotools.data.shapefile;
-
-import static org.geotools.data.shapefile.ShpFileType.SHP;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.FilenameFilter;
-import java.io.FilterInputStream;
-import java.io.FilterOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.io.RandomAccessFile;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLConnection;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.Channels;
-import java.nio.channels.FileChannel;
-import java.nio.channels.FileChannel.MapMode;
-import java.nio.channels.ReadableByteChannel;
-import java.nio.channels.WritableByteChannel;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.Set;
-import java.util.concurrent.ConcurrentHashMap;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
-import java.util.logging.Level;
-
-import org.geotools.data.DataUtilities;
-
-/**
- * Ugly copy of ShpFiles class modified to fit MapInfo TAB needs.
- */
-public class TabFiles extends ShpFiles {
-   
-    /**
-     * The urls for each type of file that is associated with the shapefile. The
-     * key is the type of file
-     */
-    private final Map<ShpFileType, URL> urls = new ConcurrentHashMap<ShpFileType, URL>();
-
-    /**
-     * A read/write lock, so that we can have concurrent readers 
-     */
-    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
-
-    /**
-     * The set of locker sources per thread. Used as a debugging aid and to upgrade/downgrade
-     * the locks
-     */
-    private final Map<Thread, Collection<ShpFilesLocker>> lockers = new ConcurrentHashMap<Thread, Collection<ShpFilesLocker>>();
-
-    /**
-     * A cache for read only memory mapped buffers
-     */
-    private final MemoryMapCache mapCache = new MemoryMapCache();
-    
-    private boolean memoryMapCacheEnabled;
-    
-    
-    ////////////////////////////////////////////////////
-    
-    public TabFiles(File headerFile, File dataFile) throws IllegalArgumentException {
-        super(fakeShpFile(headerFile)); // Useless but necessary
-        init(DataUtilities.fileToURL(headerFile));
-        urls.put(ShpFileType.DBF, DataUtilities.fileToURL(dataFile));
-    }
-    
-    /**
-     * This is really ugly. Used only to give a fake shp file to ShpFiles constructor to avoid IllegalArgument at initialization.
-     */
-    private static URL fakeShpFile(File headerFile) {
-        return DataUtilities.fileToURL(new File(headerFile.getAbsolutePath()+".shp"));
-    }
-    
-    private String baseName(Object obj) {
-        if (obj instanceof URL) {
-            return toBase(((URL) obj).toExternalForm());
-        }
-        return null;
-    }
-    
-    private String toBase(String path) {
-        return path.substring(0, path.toLowerCase().lastIndexOf(".tab"));
-    }
-
-    ////////////////////////////////////////////////////
-    
-    private void init(URL url) {
-        String base = baseName(url);
-        if (base == null) {
-            throw new IllegalArgumentException(
-                    url.getPath()
-                            + " is not one of the files types that is known to be associated with a MapInfo TAB file");
-        }
-
-        String urlString = url.toExternalForm();
-        char lastChar = urlString.charAt(urlString.length()-1);
-        boolean upperCase = Character.isUpperCase(lastChar);
-
-        for (ShpFileType type : ShpFileType.values()) {
-            
-            String extensionWithPeriod = type.extensionWithPeriod;
-            if (upperCase) {
-                extensionWithPeriod = extensionWithPeriod.toUpperCase();
-            } else {
-                extensionWithPeriod = extensionWithPeriod.toLowerCase();
-            }
-            
-            URL newURL;
-            String string = base + extensionWithPeriod;
-            try {
-                newURL = new URL(url, string);
-            } catch (MalformedURLException e) {
-                // shouldn't happen because the starting url was constructable
-                throw new RuntimeException(e);
-            }
-            urls.put(type, newURL);
-        }
-
-        // if the files are local check each file to see if it exists
-        // if not then search for a file of the same name but try all combinations of the 
-        // different cases that the extension can be made up of.
-        // IE Shp, SHP, Shp, ShP etc...
-        if (isLocalTab()) {
-            Set<Entry<ShpFileType, URL>> entries = urls.entrySet();
-            Map<ShpFileType, URL> toUpdate = new HashMap<ShpFileType, URL>();
-            for (Entry<ShpFileType, URL> entry : entries) {
-                if( !existsTab(entry.getKey()) ){
-                    url = findExistingFile(entry.getKey(), entry.getValue());
-                    if( url!=null ){
-                        toUpdate.put(entry.getKey(), url);
-                    }
-                }
-            }
-            urls.putAll(toUpdate);
-        }
-    }
-
-    private URL findExistingFile(ShpFileType shpFileType, URL value) {
-        final File file = DataUtilities.urlToFile(value);
-        File directory = file.getParentFile();
-        if( directory==null || !directory.exists() ) {
-            // doesn't exist
-            return null;
-        }
-        File[] files = directory.listFiles(new FilenameFilter(){
-
-            public boolean accept(File dir, String name) {
-                return file.getName().equalsIgnoreCase(name);
-            }
-            
-        });
-        if( files.length>0 ){
-            try {
-                return files[0].toURI().toURL();
-            } catch (MalformedURLException e) {
-                ShapefileDataStoreFactory.LOGGER.log(Level.SEVERE, "", e);
-            }
-        }
-        return null;
-    }
-
-    /**
-     * This verifies that this class has been closed correctly (nothing locking)
-     */
-    @Override
-    protected void finalize() throws Throwable {
-        super.finalize();
-        dispose();
-    }
-
-    public void dispose() {
-        if (numberOfLocks() != 0) {
-            logCurrentLockers(Level.SEVERE);
-            lockers.clear(); // so as not to get this log again.
-        }
-        mapCache.clean();
-    }
-
-    /**
-     * Writes to the log all the lockers and when they were constructed.
-     * 
-     * @param logLevel
-     *                the level at which to log.
-     */
-    public void logCurrentLockers(Level logLevel) {
-        for (Collection<ShpFilesLocker> lockerList : lockers.values()) {
-            for (ShpFilesLocker locker : lockerList) {
-                StringBuilder sb = new StringBuilder("The following locker still has a lock: ");
-                sb.append(locker);
-                ShapefileDataStoreFactory.LOGGER.log(logLevel, sb.toString(), locker.getTrace());
-            }
-        }
-    }
-
-    /**
-     * Returns the URLs (in string form) of all the files for the shapefile
-     * datastore.
-     * 
-     * @return the URLs (in string form) of all the files for the shapefile
-     *         datastore.
-     */
-    public Map<ShpFileType, String> getFileNames() {
-        Map<ShpFileType, String> result = new HashMap<ShpFileType, String>();
-        Set<Entry<ShpFileType, URL>> entries = urls.entrySet();
-
-        for (Entry<ShpFileType, URL> entry : entries) {
-            result.put(entry.getKey(), entry.getValue().toExternalForm());
-        }
-
-        return result;
-    }
-
-    /**
-     * Returns the string form of the url that identifies the file indicated by
-     * the type parameter or null if it is known that the file does not exist.
-     * 
-     * <p>
-     * Note: a URL should NOT be constructed from the string instead the URL
-     * should be obtained through calling one of the aquireLock methods.
-     * 
-     * @param type
-     *                indicates the type of file the caller is interested in.
-     * 
-     * @return the string form of the url that identifies the file indicated by
-     *         the type parameter or null if it is known that the file does not
-     *         exist.
-     */
-    public String get(ShpFileType type) {
-        return urls.get(type).toExternalForm();
-    }
-
-    /**
-     * Returns the number of locks on the current set of shapefile files. This
-     * is not thread safe so do not count on it to have a completely accurate
-     * picture but it can be useful debugging
-     * 
-     * @return the number of locks on the current set of shapefile files.
-     */
-    public int numberOfLocks() {
-        int count = 0;
-        for (Collection<ShpFilesLocker> lockerList : lockers.values()) {
-            count += lockerList.size();
-        }
-        return count;
-    }
-    /**
-     * Acquire a File for read only purposes. It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * 
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the File. The same object
-     *                must release the lock and is also used for debugging.
-     * @return the File type requested 
-     */
-    public File acquireReadFile(ShpFileType type,
-            FileReader requestor) {
-        if(!isLocalTab() ){
-            throw new IllegalStateException("This method only applies if the files are local");
-        }
-        URL url = acquireRead(type, requestor);
-        return DataUtilities.urlToFile(url);
-    }
-    
-    /**
-     * Acquire a URL for read only purposes.  It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * 
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the URL. The same object
-     *                must release the lock and is also used for debugging.
-     * @return the URL to the file of the type requested
-     */
-    public URL acquireRead(ShpFileType type, FileReader requestor) {
-        URL url = urls.get(type);
-        if (url == null)
-            return null;
-        
-        readWriteLock.readLock().lock();
-        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
-        threadLockers.add(new ShpFilesLocker(url, requestor));
-        return url;
-    }
-
-    /**
-     * Tries to acquire a URL for read only purposes. Returns null if the
-     * acquire failed or if the file does not.
-     * <p> It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * </p>
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the URL. The same object
-     *                must release the lock and is also used for debugging.
-     * @return A result object containing the URL or the reason for the failure.
-     */
-    public Result<URL, State> tryAcquireRead(ShpFileType type,
-            FileReader requestor) {
-        URL url = urls.get(type);
-        if (url == null) {
-            return new Result<URL, State>(null, State.NOT_EXIST);
-        }
-        
-        boolean locked = readWriteLock.readLock().tryLock();
-        if (!locked) {
-            return new Result<URL, State>(null, State.LOCKED);
-        }
-        
-        getCurrentThreadLockers().add(new ShpFilesLocker(url, requestor));
-
-        return new Result<URL, State>(url, State.GOOD);
-    }
-
-    /**
-     * Unlocks a read lock. The file and requestor must be the the same as the
-     * one of the lockers.
-     * 
-     * @param file
-     *                file that was locked
-     * @param requestor
-     *                the class that requested the file
-     */
-    public void unlockRead(File file, FileReader requestor) {
-        Collection<URL> allURLS = urls.values();
-        for (URL url : allURLS) {
-            if (DataUtilities.urlToFile(url).equals(file)) {
-                unlockRead(url, requestor);
-            }
-        }
-    }
-
-    /**
-     * Unlocks a read lock. The url and requestor must be the the same as the
-     * one of the lockers.
-     * 
-     * @param url
-     *                url that was locked
-     * @param requestor
-     *                the class that requested the url
-     */
-    public void unlockRead(URL url, FileReader requestor) {
-        if (url == null) {
-            throw new NullPointerException("url cannot be null");
-        }
-        if (requestor == null) {
-            throw new NullPointerException("requestor cannot be null");
-        }
-
-        Collection threadLockers = getCurrentThreadLockers();
-        boolean removed = threadLockers.remove(new ShpFilesLocker(url, requestor));
-        if (!removed) {
-            throw new IllegalArgumentException(
-                    "Expected requestor "
-                            + requestor
-                            + " to have locked the url but it does not hold the lock for the URL");
-        }
-        if(threadLockers.size() == 0)
-            lockers.remove(Thread.currentThread());
-        readWriteLock.readLock().unlock();
-    }
-
-    /**
-     * Acquire a File for read and write purposes. 
-     * <p> It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * </p>
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the File. The same object
-     *                must release the lock and is also used for debugging.
-     * @return the File to the file of the type requested
-     */
-    public File acquireWriteFile(ShpFileType type,
-            FileWriter requestor) {
-        if(!isLocalTab() ){
-            throw new IllegalStateException("This method only applies if the files are local");
-        }
-        URL url = acquireWrite(type, requestor);
-        return DataUtilities.urlToFile(url);
-    }
-    /**
-     * Acquire a URL for read and write purposes. 
-     * <p> It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * </p>
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the URL. The same object
-     *                must release the lock and is also used for debugging.
-     * @return the URL to the file of the type requested
-     */
-    public URL acquireWrite(ShpFileType type, FileWriter requestor) {
-        URL url = urls.get(type);
-        if (url == null) {
-            return null;
-        }
-
-        // we need to give up all read locks before getting the write one
-        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
-        relinquishReadLocks(threadLockers);
-        readWriteLock.writeLock().lock();
-        threadLockers.add(new ShpFilesLocker(url, requestor));
-        mapCache.cleanFileCache(url);        
-        return url;
-    }
-    
-    /**
-     * Tries to acquire a URL for read/write purposes. Returns null if the
-     * acquire failed or if the file does not exist
-     * <p> It is recommended that get*Stream or
-     * get*Channel methods are used when reading or writing to the file is
-     * desired.
-     * </p>
-     * 
-     * @see #getInputStream(ShpFileType, FileReader)
-     * @see #getReadChannel(ShpFileType, FileReader)
-     * @see #getWriteChannel(ShpFileType, FileReader)
-
-     * 
-     * @param type
-     *                the type of the file desired.
-     * @param requestor
-     *                the object that is requesting the URL. The same object
-     *                must release the lock and is also used for debugging.
-     * @return A result object containing the URL or the reason for the failure.
-     */
-    public Result<URL, State> tryAcquireWrite(ShpFileType type,
-            FileWriter requestor) {
-        
-        URL url = urls.get(type);
-        if (url == null) {
-            return new Result<URL, State>(null, State.NOT_EXIST);
-        }
-
-        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
-        boolean locked = readWriteLock.writeLock().tryLock();
-        if (!locked && threadLockers.size() > 1) {
-            // hum, it may be be because we are holding a read lock
-            relinquishReadLocks(threadLockers);
-            locked = readWriteLock.writeLock().tryLock();
-            if(locked == false) {
-                regainReadLocks(threadLockers);
-                return new Result<URL, State>(null, State.LOCKED);
-            }
-        }
-
-        threadLockers.add(new ShpFilesLocker(url, requestor));
-        return new Result<URL, State>(url, State.GOOD);
-    }
-
-    /**
-     * Unlocks a read lock. The file and requestor must be the the same as the
-     * one of the lockers.
-     * 
-     * @param file
-     *                file that was locked
-     * @param requestor
-     *                the class that requested the file
-     */
-    public void unlockWrite(File file, FileWriter requestor) {
-        Collection<URL> allURLS = urls.values();
-        for (URL url : allURLS) {
-            if (DataUtilities.urlToFile(url).equals(file)) {
-                unlockWrite(url, requestor);
-            }
-        }
-    }
-
-    /**
-     * Unlocks a read lock. The requestor must be have previously obtained a
-     * lock for the url.
-     * 
-     * 
-     * @param url
-     *                url that was locked
-     * @param requestor
-     *                the class that requested the url
-     */
-    public void unlockWrite(URL url, FileWriter requestor) {
-        if (url == null) {
-            throw new NullPointerException("url cannot be null");
-        }
-        if (requestor == null) {
-            throw new NullPointerException("requestor cannot be null");
-        }
-        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
-        boolean removed = threadLockers.remove(new ShpFilesLocker(url, requestor));
-        if (!removed) {
-            throw new IllegalArgumentException(
-                    "Expected requestor "
-                            + requestor
-                            + " to have locked the url but it does not hold the lock for the URL");
-        }
-        
-        if(threadLockers.size() == 0) {
-            lockers.remove(Thread.currentThread());
-        } else {
-            // get back read locks before giving up the write one
-            regainReadLocks(threadLockers);
-        }
-        readWriteLock.writeLock().unlock();
-    }
-   
-    /**
-     * Returns the list of lockers attached to a given thread, or creates it if missing
-     * @return
-     */
-    private Collection<ShpFilesLocker> getCurrentThreadLockers() {
-        Collection<ShpFilesLocker> threadLockers = lockers.get(Thread.currentThread());
-        if(threadLockers == null) {
-            threadLockers = new ArrayList<ShpFilesLocker>();
-            lockers.put(Thread.currentThread(), threadLockers);
-        }
-        return threadLockers;
-    }
-
-    /**
-     * Gives up all read locks in preparation for lock upgade
-     * @param threadLockers
-     */
-    private void relinquishReadLocks(Collection<ShpFilesLocker> threadLockers) {
-        for (ShpFilesLocker shpFilesLocker : threadLockers) {
-            if(shpFilesLocker.reader != null && !shpFilesLocker.upgraded) {
-                readWriteLock.readLock().unlock();
-                shpFilesLocker.upgraded = true;
-            }
-        }
-    }
-    
-    /**
-     * Re-takes the read locks in preparation for lock downgrade
-     * @param threadLockers
-     */
-    private void regainReadLocks(Collection<ShpFilesLocker> threadLockers) {
-        for (ShpFilesLocker shpFilesLocker : threadLockers) {
-            if(shpFilesLocker.reader != null && shpFilesLocker.upgraded) {
-                readWriteLock.readLock().lock();
-                shpFilesLocker.upgraded = false;
-            }
-        }
-    }
-
-    /**
-     * Determine if the location of this shapefile is local or remote.
-     * 
-     * @return true if local, false if remote
-     */
-    public boolean isLocalTab() {
-        return urls.get(ShpFileType.SHP).toExternalForm().toLowerCase().startsWith("file:");
-    }
-
-    /**
-     * Delete all the shapefile files. If the files are not local or the one
-     * files cannot be deleted return false.
-     */
-    public boolean delete() {
-        BasicShpFileWriter requestor = new BasicShpFileWriter("ShpFiles for deleting all files");
-        URL writeLockURL = acquireWrite(SHP, requestor);
-        boolean retVal = true;
-        try {
-            if (isLocalTab()) {
-                Collection<URL> values = urls.values();
-                for (URL url : values) {
-                    File f = DataUtilities.urlToFile(url);
-                    if (!f.delete()) {
-                        retVal = false;
-                    }
-                }
-            } else {
-                retVal = false;
-            }
-        } finally {
-            unlockWrite(writeLockURL, requestor);
-        }
-        return retVal;
-    }
-
-    /**
-     * Opens a input stream for the indicated file. A read lock is requested at
-     * the method call and released on close.
-     * 
-     * @param type
-     *                the type of file to open the stream to.
-     * @param requestor
-     *                the object requesting the stream
-     * @return an input stream
-     * 
-     * @throws IOException
-     *                 if a problem occurred opening the stream.
-     */
-    public InputStream getInputStream(ShpFileType type,
-            final FileReader requestor) throws IOException {
-        final URL url = acquireRead(type, requestor);
-
-        try {
-            FilterInputStream input = new FilterInputStream(url.openStream()) {
-
-                private volatile boolean closed = false;
-
-                @Override
-                public void close() throws IOException {
-                    try {
-                        super.close();
-                    } finally {
-                        if (!closed) {
-                            closed = true;
-                            unlockRead(url, requestor);
-                        }
-                    }
-                }
-
-            };
-            return input;
-        } catch (Throwable e) {
-            unlockRead(url, requestor);
-            if (e instanceof IOException) {
-                throw (IOException) e;
-            } else if( e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            } else if( e instanceof Error ) {
-                throw (Error) e;
-            } else {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-    
-    /**
-     * Opens a output stream for the indicated file. A write lock is requested at
-     * the method call and released on close.
-     * 
-     * @param type
-     *                the type of file to open the stream to.
-     * @param requestor
-     *                the object requesting the stream
-     * @return an output stream
-     * 
-     * @throws IOException
-     *                 if a problem occurred opening the stream.
-     */
-    public OutputStream getOutputStream(ShpFileType type,
-            final FileWriter requestor) throws IOException {
-        final URL url = acquireWrite(type, requestor);
-
-        try {
-            
-            OutputStream out;
-            if (isLocalTab()) {
-                File file = DataUtilities.urlToFile(url);
-                out = new FileOutputStream(file);
-            } else {
-                URLConnection connection = url.openConnection();
-                connection.setDoOutput(true);
-                out = connection.getOutputStream();
-            }
-            
-            FilterOutputStream output = new FilterOutputStream(out) {
-
-                private volatile boolean closed = false;
-
-                @Override
-                public void close() throws IOException {
-                    try {
-                        super.close();
-                    } finally {
-                        if (!closed) {
-                            closed = true;
-                            unlockWrite(url, requestor);
-                        }
-                    }
-                }
-
-            };
-
-            return output;
-        } catch (Throwable e) {
-            unlockWrite(url, requestor);
-            if (e instanceof IOException) {
-                throw (IOException) e;
-            } else if (e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            } else if (e instanceof Error) {
-                throw (Error) e;
-            } else {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    /**
-     * Obtain a ReadableByteChannel from the given URL. If the url protocol is
-     * file, a FileChannel will be returned. Otherwise a generic channel will be
-     * obtained from the urls input stream.
-     * <p>
-     * A read lock is obtained when this method is called and released when the
-     * channel is closed.
-     * </p>
-     * 
-     * @param type
-     *                the type of file to open the channel to.
-     * @param requestor
-     *                the object requesting the channel
-     * 
-     */
-    public ReadableByteChannel getReadChannel(ShpFileType type,
-            FileReader requestor) throws IOException {
-        URL url = acquireRead(type, requestor);
-        ReadableByteChannel channel = null;
-        try {
-            if (isLocalTab()) {
-
-                File file = DataUtilities.urlToFile(url);
-                
-                RandomAccessFile raf = new RandomAccessFile(file, "r");
-                channel = new FileChannelDecorator(raf.getChannel(), this, url,
-                        requestor);
-
-            } else {
-                InputStream in = url.openConnection().getInputStream();
-                channel = new ReadableByteChannelDecorator(Channels
-                        .newChannel(in), this, url, requestor);
-            }
-        } catch (Throwable e) {
-            unlockRead(url, requestor);
-            if (e instanceof IOException) {
-                throw (IOException) e;
-            } else if (e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            } else if (e instanceof Error) {
-                throw (Error) e;
-            } else {
-                throw new RuntimeException(e);
-            }
-        }
-        return channel;
-    }
-
-    /**
-     * Obtain a WritableByteChannel from the given URL. If the url protocol is
-     * file, a FileChannel will be returned. Currently, this method will return
-     * a generic channel for remote urls, however both shape and dbf writing can
-     * only occur with a local FileChannel channel.
-     * 
-     * <p>
-     * A write lock is obtained when this method is called and released when the
-     * channel is closed.
-     * </p>
-     * 
-     * 
-     * @param type
-     *                the type of file to open the stream to.
-     * @param requestor
-     *                the object requesting the stream
-     * 
-     * @return a WritableByteChannel for the provided file type
-     * 
-     * @throws IOException
-     *                 if there is an error opening the stream
-     */
-    public WritableByteChannel getWriteChannel(ShpFileType type,
-            FileWriter requestor) throws IOException {
-
-        URL url = acquireWrite(type, requestor);
-
-        try {
-            WritableByteChannel channel;
-            if (isLocalTab()) {
-
-                File file = DataUtilities.urlToFile(url);
-
-                RandomAccessFile raf = new RandomAccessFile(file, "rw");
-                channel = new FileChannelDecorator(raf.getChannel(), this, url,
-                        requestor);
-
-                ((FileChannel) channel).lock();
-
-            } else {
-                OutputStream out = url.openConnection().getOutputStream();
-                channel = new WritableByteChannelDecorator(Channels
-                        .newChannel(out), this, url, requestor);
-            }
-
-            return channel;
-        } catch (Throwable e) {
-            unlockWrite(url, requestor);
-            if (e instanceof IOException) {
-                throw (IOException) e;
-            } else if (e instanceof RuntimeException) {
-                throw (RuntimeException) e;
-            } else if (e instanceof Error) {
-                throw (Error) e;
-            } else {
-                throw new RuntimeException(e);
-            }
-        }
-    }
-
-    /**
-     * Obtains a Storage file for the type indicated. An id is provided so that
-     * the same file can be obtained at a later time with just the id
-     * 
-     * @param type
-     *                the type of file to create and return
-     * 
-     * @return StorageFile
-     * @throws IOException
-     *                 if temporary files cannot be created
-     */
-    public StorageFile getStorageFile(ShpFileType type) throws IOException {
-        String baseName = getTypeName();
-        if (baseName.length() < 3) { // min prefix length for createTempFile
-            baseName = baseName + "___".substring(0, 3 - baseName.length());
-        }
-        File tmp = File.createTempFile(baseName, type.extensionWithPeriod);
-        return new StorageFile(this, tmp, type);
-    }
-
-    public String getTypeName() {
-        String path = SHP.toBase(urls.get(SHP));
-        int slash = Math.max(0, path.lastIndexOf('/') + 1);
-        int dot = path.indexOf('.', slash);
-
-        if (dot < 0) {
-            dot = path.length();
-        }
-
-        return path.substring(slash, dot);
-    }
-    
-    /**
-     * Internal method that the file channel decorators will call to allow reuse of the memory mapped buffers
-     * @param wrapped
-     * @param url
-     * @param mode
-     * @param position
-     * @param size
-     * @return
-     * @throws IOException
-     */
-    MappedByteBuffer map(FileChannel wrapped, URL url, MapMode mode, long position, long size) throws IOException {
-        if(memoryMapCacheEnabled) {
-            return mapCache.map(wrapped, url, mode, position, size);
-        } else {
-            return wrapped.map(mode, position, size);
-        }
-    }
-    
-    /**
-     * Returns the status of the memory map cache. When enabled the memory mapped portions of the files are cached and shared
-     * (giving each thread a clone of it)
-     * @param memoryMapCacheEnabled
-     */
-    public boolean isMemoryMapCacheEnabled() {
-        return memoryMapCacheEnabled;
-    }
-
-    /**
-     * Enables the memory map cache. When enabled the memory mapped portions of the files are cached and shared
-     * (giving each thread a clone of it)
-     * @param memoryMapCacheEnabled
-     */
-    public void setMemoryMapCacheEnabled(boolean memoryMapCacheEnabled) {
-        this.memoryMapCacheEnabled = memoryMapCacheEnabled;
-        if (!memoryMapCacheEnabled) {
-            mapCache.clean();
-        }
-    }
-
-    /**
-     * Returns true if the file exists. Throws an exception if the file is not
-     * local.
-     * 
-     * @param fileType
-     *                the type of file to check existance for.
-     * 
-     * @return true if the file exists.
-     * 
-     * @throws IllegalArgumentException
-     *                 if the files are not local.
-     */
-    public boolean existsTab(ShpFileType fileType) throws IllegalArgumentException {
-        if (!isLocalTab()) {
-            throw new IllegalArgumentException(
-                    "This method only makes sense if the files are local");
-        }
-        URL url = urls.get(fileType);
-        if (url == null) {
-            return false;
-        }
-
-        File file = DataUtilities.urlToFile(url);
-        return file.exists();
-    }
-}
Index: applications/editors/josm/plugins/opendata/src/org/geotools/data/shapefile/files/TabFiles.java
===================================================================
--- applications/editors/josm/plugins/opendata/src/org/geotools/data/shapefile/files/TabFiles.java	(revision 30310)
+++ applications/editors/josm/plugins/opendata/src/org/geotools/data/shapefile/files/TabFiles.java	(revision 30310)
@@ -0,0 +1,953 @@
+//    JOSM opendata plugin.
+//    Copyright (C) 2011-2012 Don-vip
+//
+//    This program is free software: you can redistribute it and/or modify
+//    it under the terms of the GNU General Public License as published by
+//    the Free Software Foundation, either version 3 of the License, or
+//    (at your option) any later version.
+//
+//    This program is distributed in the hope that it will be useful,
+//    but WITHOUT ANY WARRANTY; without even the implied warranty of
+//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//    GNU General Public License for more details.
+//
+//    You should have received a copy of the GNU General Public License
+//    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+package org.geotools.data.shapefile.files;
+
+import static org.geotools.data.shapefile.files.ShpFileType.SHP;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.FilenameFilter;
+import java.io.FilterInputStream;
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.RandomAccessFile;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.Channels;
+import java.nio.channels.FileChannel;
+import java.nio.channels.FileChannel.MapMode;
+import java.nio.channels.ReadableByteChannel;
+import java.nio.channels.WritableByteChannel;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.logging.Level;
+
+import org.geotools.data.DataUtilities;
+
+/**
+ * Ugly copy of ShpFiles class modified to fit MapInfo TAB needs.
+ */
+public class TabFiles extends ShpFiles {
+   
+    /**
+     * The urls for each type of file that is associated with the shapefile. The
+     * key is the type of file
+     */
+    private final Map<ShpFileType, URL> urls = new ConcurrentHashMap<ShpFileType, URL>();
+
+    /**
+     * A read/write lock, so that we can have concurrent readers 
+     */
+    private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
+
+    /**
+     * The set of locker sources per thread. Used as a debugging aid and to upgrade/downgrade
+     * the locks
+     */
+    private final Map<Thread, Collection<ShpFilesLocker>> lockers = new ConcurrentHashMap<Thread, Collection<ShpFilesLocker>>();
+
+    /**
+     * A cache for read only memory mapped buffers
+     */
+    private final MemoryMapCache mapCache = new MemoryMapCache();
+    
+    private boolean memoryMapCacheEnabled;
+    
+    
+    ////////////////////////////////////////////////////
+    
+    public TabFiles(File headerFile, File dataFile) throws IllegalArgumentException {
+        super(fakeShpFile(headerFile)); // Useless but necessary
+        init(DataUtilities.fileToURL(headerFile));
+        urls.put(ShpFileType.DBF, DataUtilities.fileToURL(dataFile));
+    }
+    
+    /**
+     * This is really ugly. Used only to give a fake shp file to ShpFiles constructor to avoid IllegalArgument at initialization.
+     */
+    private static URL fakeShpFile(File headerFile) {
+        return DataUtilities.fileToURL(new File(headerFile.getAbsolutePath()+".shp"));
+    }
+    
+    private String baseName(Object obj) {
+        if (obj instanceof URL) {
+            return toBase(((URL) obj).toExternalForm());
+        }
+        return null;
+    }
+    
+    private String toBase(String path) {
+        return path.substring(0, path.toLowerCase().lastIndexOf(".tab"));
+    }
+
+    ////////////////////////////////////////////////////
+    
+    private void init(URL url) {
+        String base = baseName(url);
+        if (base == null) {
+            throw new IllegalArgumentException(
+                    url.getPath()
+                            + " is not one of the files types that is known to be associated with a MapInfo TAB file");
+        }
+
+        String urlString = url.toExternalForm();
+        char lastChar = urlString.charAt(urlString.length()-1);
+        boolean upperCase = Character.isUpperCase(lastChar);
+
+        for (ShpFileType type : ShpFileType.values()) {
+            
+            String extensionWithPeriod = type.extensionWithPeriod;
+            if (upperCase) {
+                extensionWithPeriod = extensionWithPeriod.toUpperCase();
+            } else {
+                extensionWithPeriod = extensionWithPeriod.toLowerCase();
+            }
+            
+            URL newURL;
+            String string = base + extensionWithPeriod;
+            try {
+                newURL = new URL(url, string);
+            } catch (MalformedURLException e) {
+                // shouldn't happen because the starting url was constructable
+                throw new RuntimeException(e);
+            }
+            urls.put(type, newURL);
+        }
+
+        // if the files are local check each file to see if it exists
+        // if not then search for a file of the same name but try all combinations of the 
+        // different cases that the extension can be made up of.
+        // IE Shp, SHP, Shp, ShP etc...
+        if (isLocalTab()) {
+            Set<Entry<ShpFileType, URL>> entries = urls.entrySet();
+            Map<ShpFileType, URL> toUpdate = new HashMap<ShpFileType, URL>();
+            for (Entry<ShpFileType, URL> entry : entries) {
+                if( !existsTab(entry.getKey()) ){
+                    url = findExistingFile(entry.getKey(), entry.getValue());
+                    if( url!=null ){
+                        toUpdate.put(entry.getKey(), url);
+                    }
+                }
+            }
+            urls.putAll(toUpdate);
+        }
+    }
+
+    private URL findExistingFile(ShpFileType shpFileType, URL value) {
+        final File file = DataUtilities.urlToFile(value);
+        File directory = file.getParentFile();
+        if( directory==null || !directory.exists() ) {
+            // doesn't exist
+            return null;
+        }
+        File[] files = directory.listFiles(new FilenameFilter(){
+
+            public boolean accept(File dir, String name) {
+                return file.getName().equalsIgnoreCase(name);
+            }
+            
+        });
+        if( files.length>0 ){
+            try {
+                return files[0].toURI().toURL();
+            } catch (MalformedURLException e) {
+                //ShapefileDataStoreFactory.LOGGER().log(Level.SEVERE, "", e);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * This verifies that this class has been closed correctly (nothing locking)
+     */
+    @Override
+    protected void finalize() throws Throwable {
+        super.finalize();
+        dispose();
+    }
+
+    public void dispose() {
+        if (numberOfLocks() != 0) {
+            logCurrentLockers(Level.SEVERE);
+            lockers.clear(); // so as not to get this log again.
+        }
+        mapCache.clean();
+    }
+
+    /**
+     * Writes to the log all the lockers and when they were constructed.
+     * 
+     * @param logLevel
+     *                the level at which to log.
+     */
+    public void logCurrentLockers(Level logLevel) {
+        for (Collection<ShpFilesLocker> lockerList : lockers.values()) {
+            for (ShpFilesLocker locker : lockerList) {
+                StringBuilder sb = new StringBuilder("The following locker still has a lock: ");
+                sb.append(locker);
+                //ShapefileDataStoreFactory.LOGGER().log(logLevel, sb.toString(), locker.getTrace());
+            }
+        }
+    }
+
+    /**
+     * Returns the URLs (in string form) of all the files for the shapefile
+     * datastore.
+     * 
+     * @return the URLs (in string form) of all the files for the shapefile
+     *         datastore.
+     */
+    public Map<ShpFileType, String> getFileNames() {
+        Map<ShpFileType, String> result = new HashMap<ShpFileType, String>();
+        Set<Entry<ShpFileType, URL>> entries = urls.entrySet();
+
+        for (Entry<ShpFileType, URL> entry : entries) {
+            result.put(entry.getKey(), entry.getValue().toExternalForm());
+        }
+
+        return result;
+    }
+
+    /**
+     * Returns the string form of the url that identifies the file indicated by
+     * the type parameter or null if it is known that the file does not exist.
+     * 
+     * <p>
+     * Note: a URL should NOT be constructed from the string instead the URL
+     * should be obtained through calling one of the aquireLock methods.
+     * 
+     * @param type
+     *                indicates the type of file the caller is interested in.
+     * 
+     * @return the string form of the url that identifies the file indicated by
+     *         the type parameter or null if it is known that the file does not
+     *         exist.
+     */
+    public String get(ShpFileType type) {
+        return urls.get(type).toExternalForm();
+    }
+
+    /**
+     * Returns the number of locks on the current set of shapefile files. This
+     * is not thread safe so do not count on it to have a completely accurate
+     * picture but it can be useful debugging
+     * 
+     * @return the number of locks on the current set of shapefile files.
+     */
+    public int numberOfLocks() {
+        int count = 0;
+        for (Collection<ShpFilesLocker> lockerList : lockers.values()) {
+            count += lockerList.size();
+        }
+        return count;
+    }
+    /**
+     * Acquire a File for read only purposes. It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * 
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the File. The same object
+     *                must release the lock and is also used for debugging.
+     * @return the File type requested 
+     */
+    public File acquireReadFile(ShpFileType type,
+            FileReader requestor) {
+        if(!isLocalTab() ){
+            throw new IllegalStateException("This method only applies if the files are local");
+        }
+        URL url = acquireRead(type, requestor);
+        return DataUtilities.urlToFile(url);
+    }
+    
+    /**
+     * Acquire a URL for read only purposes.  It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * 
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the URL. The same object
+     *                must release the lock and is also used for debugging.
+     * @return the URL to the file of the type requested
+     */
+    public URL acquireRead(ShpFileType type, FileReader requestor) {
+        URL url = urls.get(type);
+        if (url == null)
+            return null;
+        
+        readWriteLock.readLock().lock();
+        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
+        threadLockers.add(new ShpFilesLocker(url, requestor));
+        return url;
+    }
+
+    /**
+     * Tries to acquire a URL for read only purposes. Returns null if the
+     * acquire failed or if the file does not.
+     * <p> It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * </p>
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the URL. The same object
+     *                must release the lock and is also used for debugging.
+     * @return A result object containing the URL or the reason for the failure.
+     */
+    public Result<URL, State> tryAcquireRead(ShpFileType type,
+            FileReader requestor) {
+        URL url = urls.get(type);
+        if (url == null) {
+            return new Result<URL, State>(null, State.NOT_EXIST);
+        }
+        
+        boolean locked = readWriteLock.readLock().tryLock();
+        if (!locked) {
+            return new Result<URL, State>(null, State.LOCKED);
+        }
+        
+        getCurrentThreadLockers().add(new ShpFilesLocker(url, requestor));
+
+        return new Result<URL, State>(url, State.GOOD);
+    }
+
+    /**
+     * Unlocks a read lock. The file and requestor must be the the same as the
+     * one of the lockers.
+     * 
+     * @param file
+     *                file that was locked
+     * @param requestor
+     *                the class that requested the file
+     */
+    public void unlockRead(File file, FileReader requestor) {
+        Collection<URL> allURLS = urls.values();
+        for (URL url : allURLS) {
+            if (DataUtilities.urlToFile(url).equals(file)) {
+                unlockRead(url, requestor);
+            }
+        }
+    }
+
+    /**
+     * Unlocks a read lock. The url and requestor must be the the same as the
+     * one of the lockers.
+     * 
+     * @param url
+     *                url that was locked
+     * @param requestor
+     *                the class that requested the url
+     */
+    public void unlockRead(URL url, FileReader requestor) {
+        if (url == null) {
+            throw new NullPointerException("url cannot be null");
+        }
+        if (requestor == null) {
+            throw new NullPointerException("requestor cannot be null");
+        }
+
+        Collection threadLockers = getCurrentThreadLockers();
+        boolean removed = threadLockers.remove(new ShpFilesLocker(url, requestor));
+        if (!removed) {
+            throw new IllegalArgumentException(
+                    "Expected requestor "
+                            + requestor
+                            + " to have locked the url but it does not hold the lock for the URL");
+        }
+        if(threadLockers.size() == 0)
+            lockers.remove(Thread.currentThread());
+        readWriteLock.readLock().unlock();
+    }
+
+    /**
+     * Acquire a File for read and write purposes. 
+     * <p> It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * </p>
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the File. The same object
+     *                must release the lock and is also used for debugging.
+     * @return the File to the file of the type requested
+     */
+    public File acquireWriteFile(ShpFileType type,
+            FileWriter requestor) {
+        if(!isLocalTab() ){
+            throw new IllegalStateException("This method only applies if the files are local");
+        }
+        URL url = acquireWrite(type, requestor);
+        return DataUtilities.urlToFile(url);
+    }
+    /**
+     * Acquire a URL for read and write purposes. 
+     * <p> It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * </p>
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the URL. The same object
+     *                must release the lock and is also used for debugging.
+     * @return the URL to the file of the type requested
+     */
+    public URL acquireWrite(ShpFileType type, FileWriter requestor) {
+        URL url = urls.get(type);
+        if (url == null) {
+            return null;
+        }
+
+        // we need to give up all read locks before getting the write one
+        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
+        relinquishReadLocks(threadLockers);
+        readWriteLock.writeLock().lock();
+        threadLockers.add(new ShpFilesLocker(url, requestor));
+        mapCache.cleanFileCache(url);        
+        return url;
+    }
+    
+    /**
+     * Tries to acquire a URL for read/write purposes. Returns null if the
+     * acquire failed or if the file does not exist
+     * <p> It is recommended that get*Stream or
+     * get*Channel methods are used when reading or writing to the file is
+     * desired.
+     * </p>
+     * 
+     * @see #getInputStream(ShpFileType, FileReader)
+     * @see #getReadChannel(ShpFileType, FileReader)
+     * @see #getWriteChannel(ShpFileType, FileReader)
+
+     * 
+     * @param type
+     *                the type of the file desired.
+     * @param requestor
+     *                the object that is requesting the URL. The same object
+     *                must release the lock and is also used for debugging.
+     * @return A result object containing the URL or the reason for the failure.
+     */
+    public Result<URL, State> tryAcquireWrite(ShpFileType type,
+            FileWriter requestor) {
+        
+        URL url = urls.get(type);
+        if (url == null) {
+            return new Result<URL, State>(null, State.NOT_EXIST);
+        }
+
+        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
+        boolean locked = readWriteLock.writeLock().tryLock();
+        if (!locked && threadLockers.size() > 1) {
+            // hum, it may be be because we are holding a read lock
+            relinquishReadLocks(threadLockers);
+            locked = readWriteLock.writeLock().tryLock();
+            if(locked == false) {
+                regainReadLocks(threadLockers);
+                return new Result<URL, State>(null, State.LOCKED);
+            }
+        }
+
+        threadLockers.add(new ShpFilesLocker(url, requestor));
+        return new Result<URL, State>(url, State.GOOD);
+    }
+
+    /**
+     * Unlocks a read lock. The file and requestor must be the the same as the
+     * one of the lockers.
+     * 
+     * @param file
+     *                file that was locked
+     * @param requestor
+     *                the class that requested the file
+     */
+    public void unlockWrite(File file, FileWriter requestor) {
+        Collection<URL> allURLS = urls.values();
+        for (URL url : allURLS) {
+            if (DataUtilities.urlToFile(url).equals(file)) {
+                unlockWrite(url, requestor);
+            }
+        }
+    }
+
+    /**
+     * Unlocks a read lock. The requestor must be have previously obtained a
+     * lock for the url.
+     * 
+     * 
+     * @param url
+     *                url that was locked
+     * @param requestor
+     *                the class that requested the url
+     */
+    public void unlockWrite(URL url, FileWriter requestor) {
+        if (url == null) {
+            throw new NullPointerException("url cannot be null");
+        }
+        if (requestor == null) {
+            throw new NullPointerException("requestor cannot be null");
+        }
+        Collection<ShpFilesLocker> threadLockers = getCurrentThreadLockers();
+        boolean removed = threadLockers.remove(new ShpFilesLocker(url, requestor));
+        if (!removed) {
+            throw new IllegalArgumentException(
+                    "Expected requestor "
+                            + requestor
+                            + " to have locked the url but it does not hold the lock for the URL");
+        }
+        
+        if(threadLockers.size() == 0) {
+            lockers.remove(Thread.currentThread());
+        } else {
+            // get back read locks before giving up the write one
+            regainReadLocks(threadLockers);
+        }
+        readWriteLock.writeLock().unlock();
+    }
+   
+    /**
+     * Returns the list of lockers attached to a given thread, or creates it if missing
+     * @return
+     */
+    private Collection<ShpFilesLocker> getCurrentThreadLockers() {
+        Collection<ShpFilesLocker> threadLockers = lockers.get(Thread.currentThread());
+        if(threadLockers == null) {
+            threadLockers = new ArrayList<ShpFilesLocker>();
+            lockers.put(Thread.currentThread(), threadLockers);
+        }
+        return threadLockers;
+    }
+
+    /**
+     * Gives up all read locks in preparation for lock upgade
+     * @param threadLockers
+     */
+    private void relinquishReadLocks(Collection<ShpFilesLocker> threadLockers) {
+        for (ShpFilesLocker shpFilesLocker : threadLockers) {
+            if(shpFilesLocker.reader != null && !shpFilesLocker.upgraded) {
+                readWriteLock.readLock().unlock();
+                shpFilesLocker.upgraded = true;
+            }
+        }
+    }
+    
+    /**
+     * Re-takes the read locks in preparation for lock downgrade
+     * @param threadLockers
+     */
+    private void regainReadLocks(Collection<ShpFilesLocker> threadLockers) {
+        for (ShpFilesLocker shpFilesLocker : threadLockers) {
+            if(shpFilesLocker.reader != null && shpFilesLocker.upgraded) {
+                readWriteLock.readLock().lock();
+                shpFilesLocker.upgraded = false;
+            }
+        }
+    }
+
+    /**
+     * Determine if the location of this shapefile is local or remote.
+     * 
+     * @return true if local, false if remote
+     */
+    public boolean isLocalTab() {
+        return urls.get(ShpFileType.SHP).toExternalForm().toLowerCase().startsWith("file:");
+    }
+
+    /**
+     * Delete all the shapefile files. If the files are not local or the one
+     * files cannot be deleted return false.
+     */
+    public boolean delete() {
+        BasicShpFileWriter requestor = new BasicShpFileWriter("ShpFiles for deleting all files");
+        URL writeLockURL = acquireWrite(SHP, requestor);
+        boolean retVal = true;
+        try {
+            if (isLocalTab()) {
+                Collection<URL> values = urls.values();
+                for (URL url : values) {
+                    File f = DataUtilities.urlToFile(url);
+                    if (!f.delete()) {
+                        retVal = false;
+                    }
+                }
+            } else {
+                retVal = false;
+            }
+        } finally {
+            unlockWrite(writeLockURL, requestor);
+        }
+        return retVal;
+    }
+
+    /**
+     * Opens a input stream for the indicated file. A read lock is requested at
+     * the method call and released on close.
+     * 
+     * @param type
+     *                the type of file to open the stream to.
+     * @param requestor
+     *                the object requesting the stream
+     * @return an input stream
+     * 
+     * @throws IOException
+     *                 if a problem occurred opening the stream.
+     */
+    public InputStream getInputStream(ShpFileType type,
+            final FileReader requestor) throws IOException {
+        final URL url = acquireRead(type, requestor);
+
+        try {
+            FilterInputStream input = new FilterInputStream(url.openStream()) {
+
+                private volatile boolean closed = false;
+
+                @Override
+                public void close() throws IOException {
+                    try {
+                        super.close();
+                    } finally {
+                        if (!closed) {
+                            closed = true;
+                            unlockRead(url, requestor);
+                        }
+                    }
+                }
+
+            };
+            return input;
+        } catch (Throwable e) {
+            unlockRead(url, requestor);
+            if (e instanceof IOException) {
+                throw (IOException) e;
+            } else if( e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else if( e instanceof Error ) {
+                throw (Error) e;
+            } else {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+    
+    /**
+     * Opens a output stream for the indicated file. A write lock is requested at
+     * the method call and released on close.
+     * 
+     * @param type
+     *                the type of file to open the stream to.
+     * @param requestor
+     *                the object requesting the stream
+     * @return an output stream
+     * 
+     * @throws IOException
+     *                 if a problem occurred opening the stream.
+     */
+    public OutputStream getOutputStream(ShpFileType type,
+            final FileWriter requestor) throws IOException {
+        final URL url = acquireWrite(type, requestor);
+
+        try {
+            
+            OutputStream out;
+            if (isLocalTab()) {
+                File file = DataUtilities.urlToFile(url);
+                out = new FileOutputStream(file);
+            } else {
+                URLConnection connection = url.openConnection();
+                connection.setDoOutput(true);
+                out = connection.getOutputStream();
+            }
+            
+            FilterOutputStream output = new FilterOutputStream(out) {
+
+                private volatile boolean closed = false;
+
+                @Override
+                public void close() throws IOException {
+                    try {
+                        super.close();
+                    } finally {
+                        if (!closed) {
+                            closed = true;
+                            unlockWrite(url, requestor);
+                        }
+                    }
+                }
+
+            };
+
+            return output;
+        } catch (Throwable e) {
+            unlockWrite(url, requestor);
+            if (e instanceof IOException) {
+                throw (IOException) e;
+            } else if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else if (e instanceof Error) {
+                throw (Error) e;
+            } else {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Obtain a ReadableByteChannel from the given URL. If the url protocol is
+     * file, a FileChannel will be returned. Otherwise a generic channel will be
+     * obtained from the urls input stream.
+     * <p>
+     * A read lock is obtained when this method is called and released when the
+     * channel is closed.
+     * </p>
+     * 
+     * @param type
+     *                the type of file to open the channel to.
+     * @param requestor
+     *                the object requesting the channel
+     * 
+     */
+    public ReadableByteChannel getReadChannel(ShpFileType type,
+            FileReader requestor) throws IOException {
+        URL url = acquireRead(type, requestor);
+        ReadableByteChannel channel = null;
+        try {
+            if (isLocalTab()) {
+
+                File file = DataUtilities.urlToFile(url);
+                
+                RandomAccessFile raf = new RandomAccessFile(file, "r");
+                channel = new FileChannelDecorator(raf.getChannel(), this, url,
+                        requestor);
+
+            } else {
+                InputStream in = url.openConnection().getInputStream();
+                channel = new ReadableByteChannelDecorator(Channels
+                        .newChannel(in), this, url, requestor);
+            }
+        } catch (Throwable e) {
+            unlockRead(url, requestor);
+            if (e instanceof IOException) {
+                throw (IOException) e;
+            } else if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else if (e instanceof Error) {
+                throw (Error) e;
+            } else {
+                throw new RuntimeException(e);
+            }
+        }
+        return channel;
+    }
+
+    /**
+     * Obtain a WritableByteChannel from the given URL. If the url protocol is
+     * file, a FileChannel will be returned. Currently, this method will return
+     * a generic channel for remote urls, however both shape and dbf writing can
+     * only occur with a local FileChannel channel.
+     * 
+     * <p>
+     * A write lock is obtained when this method is called and released when the
+     * channel is closed.
+     * </p>
+     * 
+     * 
+     * @param type
+     *                the type of file to open the stream to.
+     * @param requestor
+     *                the object requesting the stream
+     * 
+     * @return a WritableByteChannel for the provided file type
+     * 
+     * @throws IOException
+     *                 if there is an error opening the stream
+     */
+    public WritableByteChannel getWriteChannel(ShpFileType type,
+            FileWriter requestor) throws IOException {
+
+        URL url = acquireWrite(type, requestor);
+
+        try {
+            WritableByteChannel channel;
+            if (isLocalTab()) {
+
+                File file = DataUtilities.urlToFile(url);
+
+                RandomAccessFile raf = new RandomAccessFile(file, "rw");
+                channel = new FileChannelDecorator(raf.getChannel(), this, url,
+                        requestor);
+
+                ((FileChannel) channel).lock();
+
+            } else {
+                OutputStream out = url.openConnection().getOutputStream();
+                channel = new WritableByteChannelDecorator(Channels
+                        .newChannel(out), this, url, requestor);
+            }
+
+            return channel;
+        } catch (Throwable e) {
+            unlockWrite(url, requestor);
+            if (e instanceof IOException) {
+                throw (IOException) e;
+            } else if (e instanceof RuntimeException) {
+                throw (RuntimeException) e;
+            } else if (e instanceof Error) {
+                throw (Error) e;
+            } else {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+
+    /**
+     * Obtains a Storage file for the type indicated. An id is provided so that
+     * the same file can be obtained at a later time with just the id
+     * 
+     * @param type
+     *                the type of file to create and return
+     * 
+     * @return StorageFile
+     * @throws IOException
+     *                 if temporary files cannot be created
+     */
+    public StorageFile getStorageFile(ShpFileType type) throws IOException {
+        String baseName = getTypeName();
+        if (baseName.length() < 3) { // min prefix length for createTempFile
+            baseName = baseName + "___".substring(0, 3 - baseName.length());
+        }
+        File tmp = File.createTempFile(baseName, type.extensionWithPeriod);
+        return new StorageFile(this, tmp, type);
+    }
+
+    public String getTypeName() {
+        String path = SHP.toBase(urls.get(SHP));
+        int slash = Math.max(0, path.lastIndexOf('/') + 1);
+        int dot = path.indexOf('.', slash);
+
+        if (dot < 0) {
+            dot = path.length();
+        }
+
+        return path.substring(slash, dot);
+    }
+    
+    /**
+     * Internal method that the file channel decorators will call to allow reuse of the memory mapped buffers
+     * @param wrapped
+     * @param url
+     * @param mode
+     * @param position
+     * @param size
+     * @return
+     * @throws IOException
+     */
+    MappedByteBuffer map(FileChannel wrapped, URL url, MapMode mode, long position, long size) throws IOException {
+        if(memoryMapCacheEnabled) {
+            return mapCache.map(wrapped, url, mode, position, size);
+        } else {
+            return wrapped.map(mode, position, size);
+        }
+    }
+    
+    /**
+     * Returns the status of the memory map cache. When enabled the memory mapped portions of the files are cached and shared
+     * (giving each thread a clone of it)
+     * @param memoryMapCacheEnabled
+     */
+    public boolean isMemoryMapCacheEnabled() {
+        return memoryMapCacheEnabled;
+    }
+
+    /**
+     * Enables the memory map cache. When enabled the memory mapped portions of the files are cached and shared
+     * (giving each thread a clone of it)
+     * @param memoryMapCacheEnabled
+     */
+    public void setMemoryMapCacheEnabled(boolean memoryMapCacheEnabled) {
+        this.memoryMapCacheEnabled = memoryMapCacheEnabled;
+        if (!memoryMapCacheEnabled) {
+            mapCache.clean();
+        }
+    }
+
+    /**
+     * Returns true if the file exists. Throws an exception if the file is not
+     * local.
+     * 
+     * @param fileType
+     *                the type of file to check existance for.
+     * 
+     * @return true if the file exists.
+     * 
+     * @throws IllegalArgumentException
+     *                 if the files are not local.
+     */
+    public boolean existsTab(ShpFileType fileType) throws IllegalArgumentException {
+        if (!isLocalTab()) {
+            throw new IllegalArgumentException(
+                    "This method only makes sense if the files are local");
+        }
+        URL url = urls.get(fileType);
+        if (url == null) {
+            return false;
+        }
+
+        File file = DataUtilities.urlToFile(url);
+        return file.exists();
+    }
+}
Index: applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/TabReader.java
===================================================================
--- applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/TabReader.java	(revision 30309)
+++ applications/editors/josm/plugins/opendata/src/org/openstreetmap/josm/plugins/opendata/core/io/geographic/TabReader.java	(revision 30310)
@@ -25,5 +25,5 @@
 import java.util.List;
 
-import org.geotools.data.shapefile.TabFiles;
+import org.geotools.data.shapefile.files.TabFiles;
 import org.geotools.data.shapefile.dbf.DbaseFileReader;
 import org.geotools.data.shapefile.dbf.DbaseFileReader.Row;
