001/* Cobertura - http://cobertura.sourceforge.net/
002 *
003 * Copyright (C) 2006 John Lewis
004 * Copyright (C) 2006 Mark Doliner
005 * Copyright (C) 2009 Chris van Es
006 *
007 * Note: This file is dual licensed under the GPL and the Apache
008 * Source License 1.1 (so that it can be used from both the main
009 * Cobertura classes and the ant tasks).
010 *
011 * Cobertura is free software; you can redistribute it and/or modify
012 * it under the terms of the GNU General Public License as published
013 * by the Free Software Foundation; either version 2 of the License,
014 * or (at your option) any later version.
015 *
016 * Cobertura is distributed in the hope that it will be useful, but
017 * WITHOUT ANY WARRANTY; without even the implied warranty of
018 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
019 * General Public License for more details.
020 *
021 * You should have received a copy of the GNU General Public License
022 * along with Cobertura; if not, write to the Free Software
023 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
024 * USA
025 */
026
027package net.sourceforge.cobertura.util;
028
029import java.io.File;
030import java.io.FileNotFoundException;
031import java.io.RandomAccessFile;
032import java.lang.reflect.InvocationTargetException;
033import java.lang.reflect.Method;
034
035/**
036 * This class controls access to any file so that multiple JVMs will
037 * not be able to write to the file at the same time.
038 *
039 * A file called "filename.lock" is created and Java's FileLock class
040 * is used to lock the file.
041 *
042 * The java.nio classes were introduced in Java 1.4, so this class
043 * does a no-op when used with Java 1.3.  The class maintains
044 * compatability with Java 1.3 by accessing the java.nio classes
045 * using reflection.
046 *
047 * @author John Lewis
048 * @author Mark Doliner
049 */
050public class FileLocker
051{
052
053        /**
054         * An object of type FileLock, created using reflection.
055         */
056        private Object lock = null;
057
058        /**
059         * An object of type FileChannel, created using reflection.
060         */
061        private Object lockChannel = null;
062
063        /**
064         * A file called "filename.lock" that resides in the same directory
065         * as "filename"
066         */
067        private File lockFile;
068
069        public FileLocker(File file)
070        {
071                String lockFileName = file.getName() + ".lock";
072                File parent = file.getParentFile();
073                if (parent == null)
074                {
075                        lockFile = new File(lockFileName);
076                }
077                else
078                {
079                        lockFile = new File(parent, lockFileName);
080                }
081        }
082
083        /**
084         * Obtains a lock on the file.  This blocks until the lock is obtained.
085         */
086        public boolean lock()
087        {
088                String useNioProperty = System.getProperty("cobertura.use.java.nio");
089                if (System.getProperty("java.version").startsWith("1.3") ||
090                                ((useNioProperty != null) && useNioProperty.equalsIgnoreCase("false")))
091                {
092                        return true;
093                }
094
095                try
096                {
097                        Class aClass = Class.forName("java.io.RandomAccessFile");
098                        Method method = aClass.getDeclaredMethod("getChannel", (Class[])null);
099                        lockChannel = method.invoke(new RandomAccessFile(lockFile, "rw"), (Object[])null);
100                }
101                catch (FileNotFoundException e)
102                {
103                        System.err.println("Unable to get lock channel for " + lockFile.getAbsolutePath()
104                                        + ": " + e.getLocalizedMessage());
105                        return false;
106                }
107                catch (InvocationTargetException e)
108                {
109                        System.err.println("Unable to get lock channel for " + lockFile.getAbsolutePath()
110                                        + ": " + e.getLocalizedMessage());
111                        return false;
112                }
113                catch (Throwable t)
114                {
115                        System.err.println("Unable to execute RandomAccessFile.getChannel() using reflection: "
116                                        + t.getLocalizedMessage());
117                        t.printStackTrace();
118                }
119
120                try
121                {
122                        Class aClass = Class.forName("java.nio.channels.FileChannel");
123                        Method method = aClass.getDeclaredMethod("lock", (Class[])null);
124                        lock = method.invoke(lockChannel, (Object[])null);
125                }
126                catch (InvocationTargetException e)
127                {
128                        System.err.println("---------------------------------------");
129                        e.printStackTrace(System.err);
130                        System.err.println("---------------------------------------");
131                        System.err.println("Unable to get lock on " + lockFile.getAbsolutePath() + ": "
132                                        + e.getLocalizedMessage());
133                        System.err.println("This is known to happen on Linux kernel 2.6.20.");
134                        System.err.println("Make sure cobertura.jar is in the root classpath of the jvm ");
135                        System.err.println("process running the instrumented code.  If the instrumented code ");
136                        System.err.println("is running in a web server, this means cobertura.jar should be in ");
137                        System.err.println("the web server's lib directory.");
138                        System.err.println("Don't put multiple copies of cobertura.jar in different WEB-INF/lib directories.");
139                        System.err.println("Only one classloader should load cobertura.  It should be the root classloader.");
140                        System.err.println("---------------------------------------");
141                        return false;
142                }
143                catch (Throwable t)
144                {
145                        System.err.println("Unable to execute FileChannel.lock() using reflection: "
146                                        + t.getLocalizedMessage());
147                        t.printStackTrace();
148                }
149
150                return true;
151        }
152
153        /**
154         * Releases the lock on the file.
155         */
156        public void release()
157        {
158                if (lock != null)
159                        lock = releaseFileLock(lock);
160                
161                if (lockChannel != null)
162                        lockChannel = closeChannel(lockChannel);
163                if (!lockFile.delete())
164                {
165                        System.err.println("lock file could not be deleted");
166                }
167        }
168
169        private static Object releaseFileLock(Object lock)
170        {
171                try
172                {
173                        Class aClass = Class.forName("java.nio.channels.FileLock");
174                        Method method = aClass.getDeclaredMethod("isValid", (Class[])null);
175                        if (((Boolean)method.invoke(lock, (Object[])null)).booleanValue())
176                        {
177                                method = aClass.getDeclaredMethod("release", (Class[])null);
178                                method.invoke(lock, (Object[])null);
179                                lock = null;
180                        }
181                }
182                catch (Throwable t)
183                {
184                        System.err.println("Unable to release locked file: " + t.getLocalizedMessage());
185                }
186                return lock;
187        }
188
189        private static Object closeChannel(Object channel)
190        {
191                try
192                {
193                        Class aClass = Class.forName("java.nio.channels.spi.AbstractInterruptibleChannel");
194                        Method method = aClass.getDeclaredMethod("isOpen", (Class[])null);
195                        if (((Boolean)method.invoke(channel, (Object[])null)).booleanValue())
196                        {
197                                method = aClass.getDeclaredMethod("close", (Class[])null);
198                                method.invoke(channel, (Object[])null);
199                                channel = null;
200                        }
201                }
202                catch (Throwable t)
203                {
204                        System.err.println("Unable to close file channel: " + t.getLocalizedMessage());
205                }
206                return channel;
207        }
208
209}