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: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.java.source.usages;
042:
043: import java.io.File;
044: import java.io.FileInputStream;
045: import java.io.FileOutputStream;
046: import java.io.IOException;
047: import java.io.InputStream;
048: import java.io.OutputStream;
049: import java.net.URISyntaxException;
050: import java.net.URL;
051: import java.util.HashMap;
052: import java.util.List;
053: import java.util.Map;
054: import java.util.Properties;
055: import java.util.Set;
056: import java.util.concurrent.atomic.AtomicBoolean;
057: import org.netbeans.api.java.source.ClassIndex;
058: import org.openide.ErrorManager;
059: import org.openide.filesystems.FileUtil;
060:
061: /**
062: * Index SPI. Represents an index for usages data
063: * @author Tomas Zezula
064: */
065: public abstract class Index {
066:
067: public enum BooleanOperator {
068: AND, OR
069: };
070:
071: private static final int VERSION = 0;
072: private static final int SUBVERSION = 8;
073: private static final String NB_USER_DIR = "netbeans.user"; //NOI18N
074: private static final String SEGMENTS_FILE = "segments"; //NOI18N
075: private static final String CLASSES = "classes"; //NOI18N
076: private static final String SLICE_PREFIX = "s"; //NOI18N
077: private static final String INDEX_DIR = "var" + File.separatorChar
078: + "cache" + File.separatorChar + "index"
079: + File.separatorChar + VERSION + '.' + SUBVERSION; //NOI18N
080:
081: private static Properties segments;
082: private static Map<String, String> invertedSegments;
083: private static File cacheFolder;
084: private static File segmentsFile;
085: private static int index = 0;
086:
087: public static final ThreadLocal<AtomicBoolean> cancel = new ThreadLocal<AtomicBoolean>() {
088: protected synchronized AtomicBoolean initialValue() {
089: return new AtomicBoolean();
090: }
091: };
092:
093: public abstract boolean isValid(boolean tryOpen) throws IOException;
094:
095: public abstract List<String> getUsagesFQN(String resourceName,
096: Set<ClassIndexImpl.UsageType> mask, BooleanOperator operator)
097: throws IOException, InterruptedException;
098:
099: public abstract <T> void getDeclaredTypes(String simpleName,
100: ClassIndex.NameKind kind, ResultConvertor<T> convertor,
101: Set<? super T> result) throws IOException,
102: InterruptedException;
103:
104: public abstract void getPackageNames(String prefix,
105: boolean directOnly, Set<String> result) throws IOException,
106: InterruptedException;
107:
108: public abstract void store(
109: Map<Pair<String, String>, List<String>> refs,
110: Set<Pair<String, String>> toDelete) throws IOException;
111:
112: public abstract void store(
113: Map<Pair<String, String>, List<String>> refs,
114: List<Pair<String, String>> topLevels) throws IOException;
115:
116: public abstract boolean isUpToDate(String resourceName,
117: long timeStamp) throws IOException;
118:
119: public abstract String getSourceName(String binaryName)
120: throws IOException;
121:
122: public abstract void clear() throws IOException;
123:
124: public abstract void close() throws IOException;
125:
126: private static void loadSegments() throws IOException {
127: if (segments == null) {
128: File cacheFolder = getCacheFolder();
129: assert cacheFolder != null;
130: segments = new Properties();
131: invertedSegments = new HashMap<String, String>();
132: segmentsFile = FileUtil.normalizeFile(new File(cacheFolder,
133: SEGMENTS_FILE));
134: if (segmentsFile.exists()) {
135: InputStream in = new FileInputStream(segmentsFile);
136: try {
137: segments.load(in);
138: } finally {
139: in.close();
140: }
141: }
142: for (Map.Entry entry : segments.entrySet()) {
143: String segment = (String) entry.getKey();
144: String root = (String) entry.getValue();
145: invertedSegments.put(root, segment);
146: try {
147: index = Math.max(index, Integer.parseInt(segment
148: .substring(SLICE_PREFIX.length())));
149: } catch (NumberFormatException nfe) {
150: ErrorManager.getDefault().notify(nfe);
151: }
152: }
153: assert segmentsFile != null;
154: }
155: }
156:
157: private static void storeSegments() throws IOException {
158: assert segmentsFile != null;
159: OutputStream out = new FileOutputStream(segmentsFile);
160: try {
161: segments.store(out, null);
162: } finally {
163: out.close();
164: }
165: }
166:
167: public static URL getSourceRootForClassFolder(final URL classFolder) {
168: if ("file".equals(classFolder.getProtocol())) { //NOI18N
169: try {
170: final File file = FileUtil.normalizeFile(new File(
171: classFolder.toURI()));
172: final File segFolder = file.getParentFile();
173: if (segFolder == null) {
174: return null;
175: }
176: final Object cFolder = segFolder.getParentFile();
177: if (cFolder == null || !cFolder.equals(cacheFolder)) {
178: return null;
179: }
180: String source = segments.getProperty(segFolder
181: .getName());
182: if (source != null) {
183: try {
184: return new URL(source);
185: } catch (IOException ioe) {
186: ErrorManager.getDefault().notify(ioe);
187: }
188: }
189: } catch (URISyntaxException e) {
190: ErrorManager.getDefault().notify(e);
191: }
192: }
193: return null;
194: }
195:
196: public static synchronized File getDataFolder(final URL root)
197: throws IOException {
198: return getDataFolder(root, false);
199: }
200:
201: public static synchronized File getDataFolder(final URL root,
202: boolean onlyIfAlreadyExists) throws IOException {
203: loadSegments();
204: final String rootName = root.toExternalForm();
205: String slice = invertedSegments.get(rootName);
206: if (slice == null) {
207: if (onlyIfAlreadyExists)
208: return null;
209: slice = SLICE_PREFIX + (++index);
210: while (segments.getProperty(slice) != null) {
211: slice = SLICE_PREFIX + (++index);
212: }
213: segments.put(slice, rootName);
214: invertedSegments.put(rootName, slice);
215: storeSegments();
216: }
217: File result = FileUtil.normalizeFile(new File(cacheFolder,
218: slice));
219: if (!result.exists()) {
220: if (onlyIfAlreadyExists)
221: return null;
222: result.mkdir();
223: }
224: return result;
225: }
226:
227: /**returns null if onlyIfAlreadyExists == true and the cache folder for the given url does not exist.
228: */
229: public static File getClassFolder(final URL url,
230: boolean onlyIfAlreadyExists) throws IOException {
231: return getClassFolderImpl(url, onlyIfAlreadyExists);
232: }
233:
234: public static File getClassFolder(final URL url) throws IOException {
235: return getClassFolder(url, false);
236: }
237:
238: public static File getClassFolder(final File root)
239: throws IOException {
240: URL url = root.toURI().toURL();
241: if (!root.exists()) {
242: final String surl = url.toExternalForm();
243: if (!surl.endsWith("/")) {
244: url = new URL(surl + '/');
245: }
246: }
247: return getClassFolderImpl(url, false);
248: }
249:
250: private static File getClassFolderImpl(final URL url,
251: boolean onlyIfAlreadyExists) throws IOException {
252: final File dataFolder = getDataFolder(url, onlyIfAlreadyExists);
253: if (onlyIfAlreadyExists && dataFolder == null)
254: return null;
255: final File result = FileUtil.normalizeFile(new File(dataFolder,
256: CLASSES));
257: if (!result.exists()) {
258: if (onlyIfAlreadyExists)
259: return null;
260: result.mkdir();
261: }
262: return result;
263: }
264:
265: /**
266: * Returns non cached netbeans user dir.
267: * For performance reasons the returned {@link File} is not normalized.
268: * Client is responsible to call {@link FileUtil.normalizeFile}
269: * before using the returned value.
270: * @return netbeans user dir.
271: */
272: static String getNbUserDir() {
273: final String nbUserProp = System.getProperty(NB_USER_DIR);
274: return nbUserProp;
275: }
276:
277: public static synchronized File getCacheFolder() {
278: if (cacheFolder == null) {
279: final String nbUserDirProp = getNbUserDir();
280: assert nbUserDirProp != null;
281: final File nbUserDir = new File(nbUserDirProp);
282: cacheFolder = FileUtil.normalizeFile(new File(nbUserDir,
283: INDEX_DIR));
284: if (!cacheFolder.exists()) {
285: boolean created = cacheFolder.mkdirs();
286: assert created : "Cannot create cache folder"; //NOI18N
287: } else {
288: assert cacheFolder.isDirectory()
289: && cacheFolder.canRead()
290: && cacheFolder.canWrite();
291: }
292: }
293: return cacheFolder;
294: }
295:
296: /**
297: * Only for unit tests!
298: *
299: */
300: static synchronized void setCacheFolder(final File folder) {
301: assert folder != null && folder.exists() && folder.canRead()
302: && folder.canWrite();
303: cacheFolder = folder;
304: }
305:
306: }
|