001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: * The Original Software is NetBeans. The Initial Developer of the Original
026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
027: * Microsystems, Inc. All Rights Reserved.
028: *
029: * If you wish your version of this file to be governed by only the CDDL
030: * or only the GPL Version 2, indicate your decision by adding
031: * "[Contributor] elects to include this software in this distribution
032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
033: * single choice of license, a recipient has the option to distribute
034: * your version of this file under either the CDDL, the GPL Version 2 or
035: * to extend the choice of license to its licensees as provided above.
036: * However, if you add GPL Version 2 code and therefore, elected the GPL
037: * Version 2 license, then the option applies only if the new code is
038: * made subject to such option by the copyright holder.
039: */
040:
041: package org.netbeans.lib.profiler.classfile;
042:
043: import org.netbeans.lib.profiler.utils.FileOrZipEntry;
044: import org.netbeans.lib.profiler.utils.MiscUtils;
045: import java.io.File;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.util.Hashtable;
049: import java.util.zip.ZipEntry;
050: import java.util.zip.ZipException;
051: import java.util.zip.ZipFile;
052:
053: /**
054: * Fixed-size cache of binary classes (.class files). Used to avoid flooding memory with class files when performing intensive
055: * method scanning, that may touch thousands of classes. Currently uses LRU eviction policy.
056: * A separate, currently no-eviction cache, is maintained for classes supplied by the VM.
057: *
058: * @author Misha Dmitirev
059: */
060: public class ClassFileCache {
061: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
062:
063: private static ClassFileCache defaultClassFileCache;
064:
065: //~ Instance fields ----------------------------------------------------------------------------------------------------------
066:
067: private ClassPath classPath; // Used to quickly obtain an open JAR file for a given name
068: private Hashtable vmSuppliedClassCache;
069: private byte[][] classFileBytes;
070: private String[] classNameAndLocation;
071: private long[] lastTimeUsed;
072: private int capacity;
073: private int size;
074: private int sizeLimit;
075: private long timeCounter;
076:
077: //~ Constructors -------------------------------------------------------------------------------------------------------------
078:
079: //------------ We don't expect the below API to be used outside of this package, hence it's package-private ------------
080: ClassFileCache() {
081: capacity = 877; // FIXME: may be worth setting size flexibly, or adjusting inside cache if too many evictions happen
082: size = 0;
083: sizeLimit = (capacity * 3) / 4;
084: classNameAndLocation = new String[capacity];
085: classFileBytes = new byte[capacity][];
086: lastTimeUsed = new long[capacity];
087:
088: vmSuppliedClassCache = new Hashtable();
089: }
090:
091: //~ Methods ------------------------------------------------------------------------------------------------------------------
092:
093: static ClassFileCache getDefault() {
094: if (defaultClassFileCache == null) {
095: defaultClassFileCache = new ClassFileCache();
096: }
097:
098: return defaultClassFileCache;
099: }
100:
101: static void resetDefaultCache() {
102: defaultClassFileCache = null;
103: }
104:
105: byte[] getClassFile(String name, String location)
106: throws IOException {
107: String nameAndLocation = (name + "#" + location).intern(); // NOI18N
108: byte[] res;
109:
110: if (location.startsWith(ClassRepository.LOCATION_VMSUPPLIED)) {
111: res = (byte[]) vmSuppliedClassCache.get(nameAndLocation);
112: } else {
113: res = get(nameAndLocation);
114:
115: if (res == null) {
116: if (size > sizeLimit) {
117: removeLRUEntry();
118: }
119:
120: res = readAndPut(name, location, nameAndLocation);
121: }
122: }
123:
124: return res;
125: }
126:
127: void addVMSuppliedClassFile(String name, int classLoaderId,
128: byte[] buf) {
129: String nameAndLocation = (name + "#"
130: + ClassRepository.LOCATION_VMSUPPLIED + classLoaderId)
131: .intern(); // NOI18N
132: vmSuppliedClassCache.put(nameAndLocation, buf);
133: }
134:
135: /**
136: * Returns the actual class loader id for the given class/loader pair, or -1 if class is not loaded.
137: * The real loader may be the same as classLoaderId or its parent loader.
138: */
139: int hasVMSuppliedClassFile(String name, int classLoaderId) {
140: do {
141: // we are trying the whole classloader hierarchy up to the root system classloader with id=0
142: boolean res = (vmSuppliedClassCache
143: .containsKey((name + "#"
144: + ClassRepository.LOCATION_VMSUPPLIED + classLoaderId)
145: .intern())); // NOI18N
146:
147: if (res) {
148: return classLoaderId;
149: } else if (classLoaderId != 0) {
150: classLoaderId = ClassLoaderTable
151: .getParentLoader(classLoaderId);
152: }
153:
154: if (classLoaderId == -1) {
155: MiscUtils
156: .printWarningMessage("Failed to lookup classloader for: "
157: + name); // NOI18N
158:
159: return -1;
160: }
161: } while (classLoaderId != 0);
162:
163: return -1;
164: }
165:
166: //---------------------------------------- Private implementation -------------------------------------------
167: private byte[] get(String nameAndLocation) {
168: int pos = (nameAndLocation.hashCode() & 0x7FFFFFFF) % capacity;
169:
170: while ((classNameAndLocation[pos] != null)
171: && (classNameAndLocation[pos] != nameAndLocation)) {
172: pos = (pos + 1) % capacity;
173: }
174:
175: if (classNameAndLocation[pos] != null) {
176: lastTimeUsed[pos] = ++timeCounter;
177:
178: return classFileBytes[pos];
179: } else {
180: return null;
181: }
182: }
183:
184: private byte[] readAndPut(String name, String classFileLocation,
185: String nameAndLocation) throws IOException {
186: byte[] classFile = readClassFile(name, classFileLocation);
187: int pos = (nameAndLocation.hashCode() & 0x7FFFFFFF) % capacity;
188:
189: while (classNameAndLocation[pos] != null) {
190: pos = (pos + 1) % capacity;
191: }
192:
193: classNameAndLocation[pos] = nameAndLocation;
194: classFileBytes[pos] = classFile;
195: lastTimeUsed[pos] = ++timeCounter;
196: size++;
197:
198: return classFile;
199: }
200:
201: private byte[] readClassFile(String name, String classFileLocation)
202: throws IOException {
203: String classFileName = name + ".class"; // NOI18N
204: File location = new File(classFileLocation);
205:
206: if (location.isDirectory()) {
207: return MiscUtils.readFileIntoBuffer(new FileOrZipEntry(
208: classFileLocation, classFileName));
209: } else { // Should be .jar file
210: // The following code may be used at different stages of JFluid work, with different initialization states, so
211: // it's coded defensively. If it can use an available open ZipFile, it will use it, otherwise it will open its own.
212:
213: ZipFile zip = null;
214:
215: if (classPath == null) {
216: classPath = ClassRepository.getClassPath();
217: }
218:
219: if (classPath != null) {
220: zip = classPath.getZipFileForName(classFileLocation);
221: }
222:
223: if (zip == null) {
224: try {
225: zip = new ZipFile(classFileLocation);
226: } catch (ZipException e2) {
227: throw new IOException("Could not open archive "
228: + classFileLocation); // NOI18N
229: }
230: }
231:
232: ZipEntry entry = zip.getEntry(classFileName);
233:
234: if (entry == null) {
235: throw new IOException("Could not find entry for "
236: + classFileName + " in " + classFileLocation); // NOI18N
237: }
238:
239: int len = (int) entry.getSize();
240: byte[] buf = new byte[len];
241: InputStream in = zip.getInputStream(entry);
242: int readBytes;
243: int ofs = 0;
244: int remBytes = len;
245:
246: do {
247: readBytes = in.read(buf, ofs, remBytes);
248: ofs += readBytes;
249: remBytes -= readBytes;
250: } while (ofs < len);
251:
252: in.close();
253:
254: return buf;
255: }
256: }
257:
258: private void removeLRUEntry() {
259: long leastTime = 0x7FFFFFFFFFFFFFFFL;
260: int pos = 0;
261:
262: for (int i = 0; i < capacity; i++) {
263: if ((lastTimeUsed[i] > 0) && (lastTimeUsed[i] < leastTime)) {
264: pos = i;
265: }
266: }
267:
268: classNameAndLocation[pos] = null;
269: classFileBytes[pos] = null;
270: lastTimeUsed[pos] = 0;
271: size--;
272:
273: return;
274: }
275: }
|