1/* Copyright (C) 2003 Vladimir Roubtsov. All rights reserved.
2 *
3 * This program and the accompanying materials are made available under
4 * the terms of the Common Public License v1.0 which accompanies this distribution,
5 * and is available at http://www.eclipse.org/legal/cpl-v10.html
6 *
7 * $Id: SourcePathCache.java,v 1.1.1.1 2004/05/09 16:57:39 vlad_r Exp $
8 */
9package com.vladium.emma.report;
10
11import java.io.File;
12import java.io.FileFilter;
13import java.util.ArrayList;
14import java.util.Collections;
15import java.util.HashMap;
16import java.util.HashSet;
17import java.util.List;
18import java.util.Map;
19import java.util.Set;
20
21import com.vladium.util.asserts.$assert;
22
23// ----------------------------------------------------------------------------
24/**
25 * @author Vlad Roubtsov, (C) 2003
26 */
27public
28final class SourcePathCache
29{
30    // public: ................................................................
31
32    // TODO: use soft cache for m_packageCache?
33
34    /**
35     * @param sourcepath [can be empty]
36     */
37    public SourcePathCache (final String [] sourcepath, final boolean removeNonExistent)
38    {
39        if (sourcepath == null) throw new IllegalArgumentException ("null input: sourcepath");
40
41        final List _sourcepath = new ArrayList (sourcepath.length);
42        for (int i = 0; i < sourcepath.length; ++ i)
43        {
44            final File dir = new File (sourcepath [i]);
45
46            if (! removeNonExistent || (dir.isDirectory () && dir.exists ()))
47                _sourcepath.add (dir);
48        }
49
50        m_sourcepath = new File [_sourcepath.size ()];
51        _sourcepath.toArray (m_sourcepath);
52
53        m_packageCache = new HashMap ();
54    }
55
56    /**
57     * @param sourcepath [can be empty]
58     */
59    public SourcePathCache (final File [] sourcepath, final boolean removeNonExistent)
60    {
61        if (sourcepath == null) throw new IllegalArgumentException ("null input: sourcepath");
62
63        final List _sourcepath = new ArrayList (sourcepath.length);
64        for (int i = 0; i < sourcepath.length; ++ i)
65        {
66            final File dir = sourcepath [i];
67
68            if (! removeNonExistent || (dir.isDirectory () && dir.exists ()))
69                _sourcepath.add (dir);
70        }
71
72        m_sourcepath = new File [_sourcepath.size ()];
73        _sourcepath.toArray (m_sourcepath);
74
75        m_packageCache = new HashMap ();
76    }
77
78    /**
79     * @return absolute pathname [null if 'name' was not found in cache]
80     */
81    public synchronized File find (final String packageVMName, final String name)
82    {
83        if (packageVMName == null) throw new IllegalArgumentException ("null input: packageVMName");
84        if (name == null) throw new IllegalArgumentException ("null input: name");
85
86        if (m_sourcepath.length == 0) return null;
87
88        CacheEntry entry = (CacheEntry) m_packageCache.get (packageVMName);
89
90        if (entry == null)
91        {
92            entry = new CacheEntry (m_sourcepath.length);
93            m_packageCache.put (packageVMName, entry);
94        }
95
96        final Set [] listings = entry.m_listings;
97        for (int p = 0; p < listings.length; ++ p)
98        {
99            Set listing = listings [p];
100            if (listing == null)
101            {
102                listing = faultListing (m_sourcepath [p], packageVMName);
103                listings [p] = listing;
104            }
105
106            // TODO: this is case-sensitive at this point
107            if (listing.contains (name))
108            {
109                final File relativeFile = new File (packageVMName.replace ('/', File.separatorChar), name);
110
111                return new File (m_sourcepath [p], relativeFile.getPath ()).getAbsoluteFile ();
112            }
113        }
114
115        return null;
116    }
117
118    // protected: .............................................................
119
120    // package: ...............................................................
121
122    // private: ...............................................................
123
124
125    private static final class CacheEntry
126    {
127        CacheEntry (final int size)
128        {
129            m_listings = new Set [size];
130        }
131
132
133        final Set /* String */ [] m_listings;
134
135    } // end of nested class
136
137
138    // NOTE: because java.io.* implements file filtering in bytecode
139    // there is no real perf advantage in using a filter here (I might
140    // as well do list() and filter the result myself
141
142    private static final class FileExtensionFilter implements FileFilter
143    {
144        public boolean accept (final File file)
145        {
146            if ($assert.ENABLED) $assert.ASSERT (file != null, "file = null");
147
148            if (file.isDirectory ()) return false; // filter out directories
149
150            final String name = file.getName ();
151            final int lastDot = name.lastIndexOf ('.');
152            if (lastDot <= 0) return false;
153
154            // [assertion: lastDot > 0]
155
156            // note: match is case sensitive
157            return m_extension.equals (name.substring (lastDot));
158        }
159
160        public String toString ()
161        {
162            return super.toString () + ", extension = [" + m_extension + "]";
163        }
164
165        FileExtensionFilter (final String extension)
166        {
167            if (extension == null)
168                throw new IllegalArgumentException ("null input: extension");
169
170            // ensure starting '.':
171            final String canonical = canonicalizeExtension (extension);
172
173            if (extension.length () <= 1)
174                throw new IllegalArgumentException ("empty input: extension");
175
176            m_extension = canonical;
177        }
178
179        private static String canonicalizeExtension (final String extension)
180        {
181            if (extension.charAt (0) != '.')
182                return ".".concat (extension);
183            else
184                return extension;
185        }
186
187
188        private final String m_extension;
189
190    } // end of nested class
191
192
193    private Set /* String */ faultListing (final File dir, final String packageVMName)
194    {
195        if ($assert.ENABLED) $assert.ASSERT (dir != null, "dir = null");
196        if ($assert.ENABLED) $assert.ASSERT (packageVMName != null, "packageVMName = null");
197
198        final File packageFile = new File (dir, packageVMName.replace ('/', File.separatorChar));
199
200        final File [] listing = packageFile.listFiles (FILE_EXTENSION_FILTER);
201
202        if ((listing == null) || (listing.length == 0))
203            return Collections.EMPTY_SET;
204        else
205        {
206            final Set result = new HashSet (listing.length);
207            for (int f = 0; f < listing.length; ++ f)
208            {
209                result.add (listing [f].getName ());
210            }
211
212            return result;
213        }
214    }
215
216
217    private final File [] m_sourcepath; // never null
218    private final Map /* packageVMName:String->CacheEntry */ m_packageCache; // never null
219
220    private static final FileExtensionFilter FILE_EXTENSION_FILTER; // set in <clinit>
221
222    static
223    {
224        FILE_EXTENSION_FILTER = new FileExtensionFilter (".java");
225    }
226
227} // end of class
228// ----------------------------------------------------------------------------