001: package tide.sources;
002:
003: import tide.editor.MainEditorFrame;
004: import snow.texteditor.SimpleDocument;
005: import snow.utils.storage.*;
006: import tide.project.*;
007: import snow.utils.StringUtils;
008: import javax.swing.*;
009: import java.awt.event.*;
010: import javax.swing.border.*;
011: import javax.swing.tree.*;
012: import java.util.*;
013: import java.util.zip.*;
014: import java.io.*;
015: import java.util.regex.*;
016:
017: /** Contains src.jar instead of rt.jar, to allow transparent source browsing of the api.
018: * For completions, use the ClassFilesManager instead !, for example it contains
019: * java.awt.geom.Point2D.Double, not only Point2D.java !
020: *
021: * TODO: use rt.jar + map src.jar if available
022: *
023: * Exist only once, clearModel() and add() allow changing project.
024: * at first level, one finds the lib roots. Either directories or jars or zips.
025: * [Aug2006] only classes are shown in the tree, but sources are mapped in a hashtable.
026: */
027: public final class LibrariesTreeModel extends DefaultTreeModel
028: implements JavaFilesTreeModel {
029: // must be manually kept uptodate ! (key = uppercased javaName()) !!
030: private final Hashtable<String, LibFileItem> quickCache = new Hashtable<String, LibFileItem>();
031: private final Hashtable<String, LibFileItem> quickPackageCache = new Hashtable<String, LibFileItem>();
032: // maps sources (String java name)
033: private final Hashtable<String, SourceWrapper> sources = new Hashtable<String, SourceWrapper>();
034:
035: // Set lazily from the LibClassesInfo at "project reload"
036: public final Set<String> knownAnnotations = new HashSet<String>();
037: public final Set<String> knownInterfaces = new HashSet<String>();
038:
039: public final LibFileItem root;
040:
041: public LibrariesTreeModel() {
042: super (new LibFileItem("Libraries", "", ""));
043: this .root = (LibFileItem) this .getRoot();
044: }
045:
046: private void addToQuickCache(LibFileItem fi) {
047: if (fi.isDirectory()) {
048: addToQuickPackageCache(fi);
049: } else {
050: quickCache.put(
051: fi.getJavaName().toUpperCase(Locale.ENGLISH), fi);
052: }
053: }
054:
055: public FileItem quickGet(String javaName, boolean caseSensitive) {
056: FileItem it = quickCache.get(javaName
057: .toUpperCase(Locale.ENGLISH));
058: if (it != null) {
059: if (!caseSensitive)
060: return it; // ok
061:
062: if (it.getJavaName().equals(javaName))
063: return it;
064: }
065: return null;
066: }
067:
068: private void addToQuickPackageCache(LibFileItem fi) {
069: quickPackageCache.put(fi.getJavaName().toUpperCase(
070: Locale.ENGLISH), fi);
071: }
072:
073: public FileItem quickPackageGet(String javaName,
074: boolean caseSensitive) {
075: if (javaName == null)
076: throw new NullPointerException("null javaName !");
077: FileItem it = quickPackageCache.get(javaName
078: .toUpperCase(Locale.ENGLISH));
079: if (it != null) {
080: if (!caseSensitive)
081: return it; // ok
082:
083: if (it.getJavaName().equals(javaName))
084: return it;
085: }
086: return null;
087: }
088:
089: /** important: also call this when changing project, this releases the zip files references...
090: */
091: public void clearModel() {
092: for (int i = this .root.getChildCount() - 1; i >= 0; i--) {
093: LibFileItem li = this .root.getChildItemAt(i);
094: li.liberateResourcesForGC();
095: //li.removeFromParent(); // TODO: recurse ??
096: this .removeNodeFromParent(li); // so we really UI remove !!
097: }
098: quickCache.clear();
099: quickPackageCache.clear();
100: sources.clear();
101: knownAnnotations.clear();
102: knownInterfaces.clear();
103: }
104:
105: /** @param librariesRoots dirs and jars
106: */
107: public void setRoots(List<File> librariesRoots,
108: List<File> sourcesOfLibraries) {
109: this .clearModel();
110: long t0 = System.currentTimeMillis();
111: SimpleDocument doc = MainEditorFrame.instance.outputPanels.tideOutputPanel.doc;
112: for (File f : librariesRoots) {
113: if (f.exists()) {
114: addRoot(f, false);
115: } else {
116: doc.appendErrorLine("Library not found: " + f);
117: }
118: }
119:
120: for (File f : sourcesOfLibraries) {
121: if (f.exists()) {
122: addRoot(f, true);
123: } else {
124: doc.appendErrorLine("Source archive not found: " + f);
125: }
126: }
127:
128: // just an info...
129: long dt = System.currentTimeMillis() - t0;
130: MainEditorFrame.debugOut("" + quickCache.size()
131: + " library class files read in " + dt + " ms");
132: MainEditorFrame.debugOut("" + sources.size()
133: + " sources mapped in the library model");
134: }
135:
136: /** Add root items of the class path, either dirs, jars or zips
137: KEEPING THE ORDER AS THEY APPEAR IN THE CLASS PATH
138: */
139: private void addRoot(File f, boolean isSource) {
140: if (f.isDirectory()) {
141: LibFileItem.LibDirRoot lf = new LibFileItem.LibDirRoot(f);
142: // also add if source, because of releasing of resources... (TODO: better)
143: this .insertNodeInto(lf, root, root.getChildCount()); // ok, keep order of class path
144: if (!MainEditorFrame.lowMemoryMode) {
145: addAll(f, f, lf, isSource);
146: }
147: } else {
148: String flow = f.getName().toLowerCase(Locale.ENGLISH);
149: if (flow.endsWith(".zip") || flow.endsWith(".jar")) {
150: try {
151: if (!MainEditorFrame.lowMemoryMode) {
152: ZipFile zf = new ZipFile(f, ZipFile.OPEN_READ);
153: LibFileItem.LibFileRoot lf = new LibFileItem.LibFileRoot(
154: zf, f.getName());
155: this .insertNodeInto(lf, root, root
156: .getChildCount()); // ok, keep order of class path
157: addAll(zf, lf, isSource);
158: } else {
159: LibFileItem.LibFileRootLOWMEM lf = new LibFileItem.LibFileRootLOWMEM(
160: f.getAbsolutePath());
161: this .insertNodeInto(lf, root, root
162: .getChildCount());
163: }
164: } catch (Exception ex) {
165: ex.printStackTrace();
166: LibFileItem lf = new LibFileItem(f.getName()
167: + " ERROR: " + ex.getMessage(), "", "");
168: this .insertNodeInto(lf, root, root.getChildCount()); // ok, keep order of class path
169: }
170: }
171: }
172: /*else
173: {
174: // ignore !
175: }*/
176: }
177:
178: /** TODO: test (and DETECT filenames with ".")
179: * TODO: sources => in sources
180: */
181: private void addAll(File dir, File rootDir, LibFileItem lf,
182: boolean isSource) {
183: new Throwable("NOT IMPL").printStackTrace();
184:
185: File[] files = dir.listFiles();
186: String base = rootDir.getAbsolutePath().replace('\\', '/');
187: if (!base.endsWith("/"))
188: base += "/";
189:
190: if (files != null) {
191: for (File f : files) {
192: String javaName = f.getAbsolutePath()
193: .replace('\\', '/');
194: javaName = javaName.substring(base.length()).replace(
195: '/', '.');
196:
197: if (f.isDirectory()) {
198: LibFileItem lfc = new LibFileItem(f.getName(),
199: javaName, javaName);
200: SourceFileUtils.insertSorted(lfc, lf, this );
201: addAll(f, rootDir, lfc, isSource);
202: addToQuickCache(lfc);
203: } else {
204: if (javaName.toLowerCase().endsWith(".java")) {
205: javaName = javaName.substring(0, javaName
206: .length() - 5);
207: } else if (javaName.toLowerCase()
208: .endsWith(".class")) {
209: javaName = javaName.substring(0, javaName
210: .length() - 6);
211: }
212:
213: String packageName = StringUtils
214: .removeAfterLastIncluded(javaName, ".");
215: LibFileItem lfc = new LibFileItem(f.getName(),
216: javaName, packageName);
217: addToQuickCache(lfc);
218: SourceFileUtils.insertSorted(lfc, lf, this );
219: }
220: }
221: }
222: }
223:
224: /** if isSource, the tree is not created, but the sources are stored in the sources store,
225: * for later mapping when user shows the class.
226: */
227: private void addAll(ZipFile zf, LibFileItem rootZip,
228: boolean isSource) {
229: for (Enumeration<? extends ZipEntry> en = zf.entries(); en
230: .hasMoreElements();) {
231: ZipEntry ze = en.nextElement();
232:
233: if (ze.isDirectory())
234: continue; // ignore dir entries...
235:
236: String name = ze.getName().trim().replace('\\', '/');
237: if (name.startsWith("/")) {
238: name = name.substring(1);
239: }
240: if (name.endsWith("/")) {
241: name = name.substring(0, name.length() - 1);
242: }
243: String[] path = name.split("/"); // a/b/c.java => {a,b,c.java}
244:
245: LibFileItem target = rootZip;
246: String packageName = "";
247:
248: for (int i = 0; i < path.length; i++) {
249: if (i > 0)
250: packageName += ".";
251: packageName += path[i];
252:
253: LibFileItem ci = target.getChildNamed(path[i]);
254: if (ci == null || i == path.length - 1) {
255: // create
256: if (i == path.length - 1) {
257: if (path[i].toLowerCase().endsWith(".java")) {
258: sources.put(name.substring(0,
259: name.length() - 5)
260: .replace("/", "."),
261: new SourceWrapper(zf, ze));
262: } else {
263: // class
264: ci = new LibFileItem(ze, path[i], zf);
265: }
266: } else {
267: ci = new LibFileItem(path[i], packageName,
268: packageName, zf);
269: }
270:
271: if (ci != null && !isSource) // sources are not added in the tree
272: {
273: addToQuickCache(ci);
274: //this.insertNodeInto( ci, target, target.getChildCount());
275: SourceFileUtils.insertSorted(ci, target, this );
276: }
277: }
278: target = ci;
279: }
280: }
281: }
282:
283: /** @return the package file (folder) for the given java name (ex: java.util)
284: *@deprecated this returns only the first match !
285: * javax may be present in several jars !!!
286: */
287: @Deprecated
288: public FileItem getPackage(String javaName) {
289: String[] path = javaName.split("\\.");
290: // over roots (libs)
291: rl: for (int lib = 0; lib < root.getChildCount(); lib++) {
292: LibFileItem actNode = root.getChildItemAt(lib);
293: for (String pi : path) {
294: actNode = getJavaChild(actNode, pi, null);
295: if (actNode == null)
296: continue rl;
297: }
298: // found !
299: return actNode;
300: }
301:
302: // not found
303: return null;
304: }
305:
306: /** Looks in each library.
307: */
308: public List<FileItem> getAllPackages(String javaName) {
309: List<FileItem> packs = new ArrayList<FileItem>();
310:
311: String[] path = javaName.split("\\.");
312: // over roots (libs)
313: rl: for (int lib = 0; lib < root.getChildCount(); lib++) {
314: LibFileItem actNode = root.getChildItemAt(lib);
315: for (String pi : path) {
316: actNode = getJavaChild(actNode, pi, null);
317: if (actNode == null)
318: continue rl; // look in the next lib.
319: }
320: // found !
321: packs.add(actNode);
322: }
323:
324: return packs;
325: }
326:
327: /** Top level libraries (rt.jar, src.zip, ...).
328: */
329: public List<LibFileItem> getAllTopLevelLibs() {
330: List<LibFileItem> ret = new ArrayList<LibFileItem>();
331: for (int lib = 0; lib < root.getChildCount(); lib++) {
332: LibFileItem actNode = root.getChildItemAt(lib);
333: ret.add(actNode);
334: }
335: return ret;
336: }
337:
338: /** @param javaName is the full class name (NOT the package name)
339: @param fileName if not empty is to be used when the type has not the name of the file !
340: @return null if not found
341:
342: WARNING: may not respect priorities in case of conflicts...
343: but respects the lib order.
344: */
345: public LibFileItem getFile(String javaName, String fileName) {
346: if (javaName.equals(""))
347: return null;
348: String[] path = javaName.split("\\.");
349:
350: // over roots (libs)
351: rl: for (int lib = 0; lib < root.getChildCount(); lib++) {
352: LibFileItem actNode = root.getChildItemAt(lib);
353: //System.out.println("Look in lib "+actNode+" for "+Arrays.toString(path)+" ("+fileName+")");
354:
355: for (int i = 0; i < path.length; i++) {
356: String ni = path[i];
357: LibFileItem newNode = getJavaChild(actNode, ni,
358: fileName);
359: if (newNode == null) {
360: //System.out.println("Not found : "+ni+" in "+actNode);
361: continue rl; // look in the next lib
362: } else if (newNode.isJavaFile()) {
363: return newNode;
364: }
365:
366: actNode = newNode;
367: }
368:
369: // DO NOT RETURN !, look in the next lib !
370: }
371:
372: // not found
373: return null;
374: }
375:
376: /**
377: @param fileName, if not empty is to be used when the type has not the name of the file !
378: may occur for non public classes (even package scope !!)
379: */
380: private LibFileItem getJavaChild(LibFileItem parent,
381: String javaPartName, String fileName) {
382: // search for a dir or java file
383: for (int i = 0; i < parent.getChildCount(); i++) {
384: LibFileItem pi = parent.getChildItemAt(i);
385: if (pi.getJavaPartName().equals(javaPartName))
386: return pi;
387: }
388:
389: // search for the file name
390: if (fileName != null && fileName.length() > 0) {
391: for (int i = 0; i < parent.getChildCount(); i++) {
392: LibFileItem pi = parent.getChildItemAt(i);
393: if (pi.getName().equals(fileName))
394: return pi;
395: }
396: }
397:
398: return null;
399: }
400:
401: /** TODO: this could be cached, removed at clearModel() and recreated at addRoot() !
402: * even non java ones !
403: */
404: public List<LibFileItem> getAllFiles() {
405: ArrayList<LibFileItem> items = new ArrayList<LibFileItem>();
406: getAllFilesRecurse(items, root, false); // even non java ones !
407: return items;
408: }
409:
410: /** Everything but directories.
411: */
412: public void getAllFilesRecurse(List<LibFileItem> items,
413: LibFileItem froot, boolean javaOnly) {
414: for (int i = 0; i < froot.getChildCount(); i++) {
415: getAllFilesRecurse(items, froot.getChildItemAt(i), javaOnly);
416: }
417:
418: if (froot.isFile) {
419: if (!javaOnly || froot.isJavaFile()) {
420: items.add(froot);
421: }
422: }
423: }
424:
425: /** Every directories (packages).
426: */
427: public void getAllDirsRecurse(List<LibFileItem> items,
428: LibFileItem froot) {
429: for (int i = 0; i < froot.getChildCount(); i++) {
430: getAllDirsRecurse(items, froot.getChildItemAt(i));
431: }
432:
433: // TODO.
434: if (!froot.isFile && !(froot instanceof LibFileItem.LibDirRoot)
435: && !(froot instanceof LibFileItem.LibFileRoot)) {
436: items.add(froot);
437: }
438: }
439:
440: public List<LibFileItem> getAllJavaFilesWithSimpleNameStartingWith(
441: String start) {
442: List<LibFileItem> allSrcFiles = new ArrayList<LibFileItem>();
443: getAllFilesRecurse(allSrcFiles, root, true);
444:
445: List<LibFileItem> rep = new ArrayList<LibFileItem>();
446: for (LibFileItem sfi : allSrcFiles) {
447: String partName = sfi.getJavaPartName();
448: if (partName.startsWith(start))
449: rep.add(sfi);
450: }
451: return rep;
452: }
453:
454: /** Useful to locate missing imports (slow).
455: * @param javaOnly if true, only .class and .java are searched.
456: */
457: public List<LibFileItem> getAllFilesContainingName(
458: String javaPartName, boolean caseSensitive, boolean exact,
459: boolean javaOnly) {
460: if (!caseSensitive) {
461: javaPartName = javaPartName.toUpperCase(Locale.ENGLISH);
462: }
463:
464: List<LibFileItem> allSrcFiles = new ArrayList<LibFileItem>();
465: getAllFilesRecurse(allSrcFiles, root, javaOnly);
466:
467: List<LibFileItem> rep = new ArrayList<LibFileItem>();
468: for (LibFileItem sfi : allSrcFiles) {
469: String partName = sfi.getJavaPartName();
470: if (!caseSensitive) {
471: partName = partName.toUpperCase(Locale.ENGLISH);
472: }
473: if (exact) {
474: if (partName.equals(javaPartName)) {
475: rep.add(sfi);
476: }
477: } else {
478: if (partName.indexOf(javaPartName) >= 0) {
479: rep.add(sfi);
480: }
481: }
482: }
483:
484: return rep;
485: }
486:
487: /** @return null if not found
488: */
489: String getSourceFor(String javaName) throws Exception {
490: // internal classes
491: if (javaName.indexOf('$') > 0) {
492: javaName = javaName.substring(0, javaName.indexOf('$'));
493: }
494: if (!sources.containsKey(javaName))
495: return null;
496: SourceWrapper sw = sources.get(javaName);
497: return sw.getSourceContent();
498: }
499:
500: /** Wraps a source. Used to find the source for some class
501: */
502: private class SourceWrapper {
503: // if the source is in a file
504: final File src;
505: final ZipEntry ze;
506: final ZipFile zf;
507:
508: public SourceWrapper(File src) {
509: this .src = src;
510: this .ze = null;
511: this .zf = null;
512: }
513:
514: public SourceWrapper(ZipFile zf, ZipEntry ze) {
515: this .src = null;
516: this .ze = ze;
517: this .zf = zf;
518: }
519:
520: public String getSourceContent() throws Exception {
521: if (src != null) {
522: //if(!src.exists()
523: return FileUtils.getFileStringContent(src);
524: }
525:
526: InputStream is = null;
527: try {
528: StringBuilder sb = new StringBuilder();
529: is = zf.getInputStream(ze);
530: InputStreamReader r = new InputStreamReader(is);
531: char[] buf = new char[256];
532: int read = -1;
533: while ((read = r.read(buf)) != -1) {
534: sb.append(new String(buf, 0, read));
535: }
536: return sb.toString();
537: } catch (Exception e) {
538: throw e;
539: } finally {
540: FileUtils.closeIgnoringExceptions(is);
541: }
542: }
543: }
544:
545: }
|