001: package tide.project;
002:
003: import java.util.concurrent.*;
004: import java.util.*;
005: import java.io.*;
006: import java.util.zip.*;
007: import snow.utils.storage.FileUtils;
008: import tide.utils.FileUtilities;
009: import tide.editor.MainEditorFrame;
010: import tide.sources.SourceFile;
011:
012: /** Manages the classes files (from classpath and the generated ones).
013: * is associated to a project.
014: *
015: * Include rt.jar (all java API classes)
016: * Should be used for classes locations instead of the libs tree (that also contain sources)
017: *
018: * load classes such as java.awt.geom.Point2D.Double ! (enclosed public classes)
019: * ( java/awt/geom/Point2D$Double.class => java.awt.geom.Point2D$Double )
020: * no problem, replace file names "$" with ".".
021: *
022: * PROBLEM: source file A may contain toplevel private classes named B => create b.class, not A$B !
023: * no way to find out what it is (?) from the simple file name !
024: *
025: * TODO: allow rescanning from a root (deleting root.* and rescan root)
026: *
027: * TODO: allow only update new class files (use the last op system time and create a quick get)
028: *
029: * Also keep old classes for completion classesCacheForCompletion. To avoid duplicate class exception when moving or refactoring from
030: * inner class to standalone, the cache is emptyed on successful global build of branchs!
031: *
032: * Oct: removed the toUppercase
033: */
034: public final class ClassFilesManager {
035: // ext libs (key = java name with case) (TAKES MUCH PLACE!)
036: final private HashMap<String, ProjClass> classPathClasses = new HashMap<String, ProjClass>();
037: //final private List<ProjClass> sortedClassPathClasses = new ArrayList<ProjClass>();
038:
039: // generated
040: final private HashMap<String, ProjClass> generatedClasses = new HashMap<String, ProjClass>();
041:
042: // cached classes, copied to classesCacheForCompletion. Used for completion !
043: final private HashMap<String, ProjClass> generatedClassesCache = new HashMap<String, ProjClass>();
044:
045: // keep ref to be able to close them
046: final List<ZipFile> openedZipFiles = new ArrayList<ZipFile>();
047:
048: final File generatedClassesRoot;
049: final File classesCacheForCompletion;
050: final List<File> classPath;
051:
052: /** Ensures that the analysis runs one at a time. Also useful to quit the compile thread, because this is not
053: * important to wait.
054: */
055: final private ExecutorService analysisExecutionService = Executors
056: .newFixedThreadPool(1);
057:
058: public ClassFilesManager(List<File> classPath,
059: File generatedClassesRoot, File classesCacheForCompletion) {
060: this .generatedClassesRoot = generatedClassesRoot;
061: this .classesCacheForCompletion = classesCacheForCompletion;
062: this .classPath = classPath;
063:
064: long t0 = System.currentTimeMillis();
065:
066: // enqueued
067: Future fut = analyseGeneratedClasses();
068:
069: // not enqueued, has nothing to do with the project classes (is rt.jar, ...).
070: reanalyseClassPath();
071:
072: // WAITS. (the two above tasks may run in parallel !)
073: try {
074: fut.get();
075: } catch (Exception ign) {
076: }
077:
078: // always print it out, it is also a kind of benchmark
079: long dt = System.currentTimeMillis() - t0;
080: MainEditorFrame.debugOut("ClassFilesManager: "
081: + classPathClasses.size() + " classes in libs, "
082: + generatedClasses.size() + " in project. ("
083: + generatedClassesCache.size()
084: + " in cache), analysed in " + dt + " ms");
085:
086: if (!classesCacheForCompletion.exists()) {
087: classesCacheForCompletion.mkdirs();
088: }
089:
090: }
091:
092: public void remove(String javaName) {
093: // todo: delete the classes, not just remove...
094: //generatedClasses.get(javaName).
095: generatedClasses.remove(javaName);
096: generatedClassesCache.remove(javaName);
097: //no: classPathClasses
098: }
099:
100: /** @param name for example "java.util.Vector"...
101: * Search order: generated classes, ext libs
102: */
103: public ProjClass getClassExact(final String name) {
104: String nameN = name.replace('$', '.'); // inner classes
105:
106: ProjClass pc = generatedClasses.get(nameN);
107: if (pc != null)
108: return pc;
109:
110: //System.out.println("getClassExact("+name+") not found in project: "+generatedClasses.keySet());
111:
112: pc = generatedClassesCache.get(nameN);
113: if (pc != null)
114: return pc;
115:
116: //int pos = Collections.binarySearch(sortedClassPathClasses, nameN);
117:
118: pc = classPathClasses.get(nameN);
119: if (pc != null)
120: return pc;
121:
122: /*MainEditorFrame.debugOut(""+name+" not found in cfm ("
123: +generatedClasses.size()+", "+generatedClassesCache.size()
124: +", "+classPathClasses.size()+" classes)");
125: */
126: return null;
127: }
128:
129: /* @param name for example "util.Vector"...
130: * search order: generated classes, ext libs
131: * BAD ! ONLY FOR HELP !
132: *
133: public ProjClass getClassEndingWith(String name)
134: {
135: for(ProjClass pc : generatedClasses)
136: {
137: if(pc.getJavaName().endsWith(name)) return pc;
138: }
139:
140: for(ProjClass pc : classPathClasses)
141: {
142: if(pc.getJavaName().endsWith(name)) return pc;
143: }
144:
145: return null;
146: }*/
147:
148: /** Must be called after each compilation... and at startup. Also on failure.
149: * Immediately returns, the result is an ENQUEUED task.
150: */
151: public Future analyseGeneratedClasses() {
152: return analysisExecutionService.submit(new Runnable() {
153: public void run() {
154: long t0 = System.currentTimeMillis();
155: MainEditorFrame
156: .debugOut("\r\ncfm.analyseGeneratedClasses");
157:
158: generatedClasses.clear();
159: addAllClassesFromDir(generatedClasses,
160: generatedClassesRoot);
161:
162: generatedClassesCache.clear();
163: addAllClassesFromDir(generatedClassesCache,
164: classesCacheForCompletion);
165:
166: // 3 sec for 2 * 6'000 classes. OK.
167: MainEditorFrame.debugOut("cfm.analyse_end ("
168: + (System.currentTimeMillis() - t0) + " ms, "
169: + generatedClassesCache.size() + " cla)");
170:
171: }
172: });
173: }
174:
175: /** Called for "XXX/classes" and "XXX/.tide/classesTemp".
176: * TODO: diff update, only reput if not already contained
177: */
178: private static void addAllClassesFromDir(
179: final Map<String, ProjClass> vc, final File root) {
180: //MainEditorFrame.debugOut("CFM:add all classes from "+root);
181: int baseNameLength = FileUtils.getCanonicalName(root).length();
182: final List<File> cfs = new ArrayList<File>();
183: FileUtils.getAllClassFilesRecurse(root, cfs, true);
184: MainEditorFrame.debugOut("CFM: " + cfs.size()
185: + " classes added from " + root);
186:
187: for (final File cf : cfs) {
188: String jn = FileUtils.getCanonicalName(cf).substring(
189: baseNameLength);
190: jn = createJavaNameFromClassFileName(jn);
191: //if(!vc.containsKey(jn)) ... (?)
192: vc.put(jn, new ProjClass(cf, jn));
193: }
194: cfs.clear();
195: }
196:
197: /** Add all classes from classpath (currently only zip and jars).
198: * Only called from constructor at new creation.
199: */
200: private void reanalyseClassPath() {
201: //MainEditorFrame.debugOut("fcm.reanalyse");
202: classPathClasses.clear();
203: for (File cpf : classPath) {
204: if (!cpf.exists()) {
205: MainEditorFrame.instance.outputPanels.tideOutputPanel.doc
206: .appendErrorLine("Classpath file not found: "
207: + cpf);
208: continue;
209: }
210:
211: if (cpf.isDirectory()) {
212: addAllClassesFromDir(classPathClasses, cpf);
213: } else {
214: String fn = cpf.getName().toLowerCase();
215: if (fn.endsWith(".zip") || fn.endsWith(".jar")) {
216: try {
217: addAllClassesFromArchive(classPathClasses, cpf);
218: } catch (Exception e) {
219: e.printStackTrace();
220: }
221: } else {
222: System.out
223: .println("Unknown classpath item: " + cpf);
224: }
225: }
226: }
227: }
228:
229: private static String createJavaNameFromClassFileName(
230: final String cn) {
231: return cn.substring(0, cn.length() - 6).replace('/', '.')
232: .replace('$', '.');
233: }
234:
235: private void addAllClassesFromArchive(Map<String, ProjClass> vc,
236: File archive) throws Exception {
237: try {
238: ZipFile zf = new ZipFile(archive);
239: Enumeration<? extends ZipEntry> entries = zf.entries();
240: while (entries.hasMoreElements()) {
241: ZipEntry ze = entries.nextElement();
242: String cn = ze.getName();
243: if (cn.toLowerCase().endsWith(".class")) {
244: String jn = createJavaNameFromClassFileName(cn);
245: vc.put(jn,
246: //.toUpperCase(Locale.ENGLISH),
247: new ProjClass(ze, zf));
248: }
249: }
250: } catch (Exception e) {
251: throw e;
252: }
253: }
254:
255: /** Called before compiling or on project clear.
256: * HERE IS THE ONLY POINT WHERE CLASSES ARE DELETED !
257: */
258: public void deleteAllClassesOnDiskAndWait() {
259: final Future fut = analysisExecutionService
260: .submit(new Runnable() {
261: public void run() {
262: long t0 = System.currentTimeMillis();
263: MainEditorFrame
264: .debugOut("\r\ncfm.deleteAllClassesOnDisk");
265:
266: final ProjectSettings actualProject = MainEditorFrame.instance
267: .getActualProject();
268: final int baseNameLength = FileUtils
269: .getCanonicalName(
270: actualProject.getClasses_Home())
271: .length();
272:
273: final List<File> allClasses = new ArrayList<File>();
274: FileUtils.getAllClassFilesRecurse(actualProject
275: .getClasses_Home(), allClasses, true);
276:
277: //FileUtilities.getAllFilesRecurse_withNameEndingWith(allClasses, actualProject.getClasses_Home(), ".class");
278: /* MainEditorFrame.instance.outputPanels.tideOutputPanel.doc.appendLine(
279: "Deleting all " + allClasses.size() + " generated classes");*/
280: long t1 = System.currentTimeMillis();
281: for (final File cf : allClasses) {
282: String baseName = FileUtils
283: .getCanonicalName(cf).substring(
284: baseNameLength);
285: copyGeneratedClasseInCache(cf, baseName,
286: true); // DELETE the source...
287: }
288: MainEditorFrame.debugOut("cfm: "
289: + allClasses.size()
290: + " classes moved to the cache ("
291: + (System.currentTimeMillis() - t1)
292: + " ms)");
293:
294: // remove empty directories
295: final List<File> allDirs = new ArrayList<File>();
296: t1 = System.currentTimeMillis();
297: FileUtilities
298: .getAllDirectoriesRecurse_depthFirst(
299: allDirs, actualProject
300: .getClasses_Home()); // depth first is important here
301:
302: for (final File d : allDirs) {
303: if (!d.delete()) {
304: System.out
305: .println("CFM: Cannot delete dir: "
306: + d
307: + " ("
308: + d.listFiles().length
309: + " files)");
310: }
311: }
312: MainEditorFrame.debugOut("deleted "
313: + allDirs.size() + " classes dirs ("
314: + (System.currentTimeMillis() - t1)
315: + " ms)");
316:
317: MainEditorFrame
318: .debugOut("cfm.deleteAllClassesOnDisk done ("
319: + (System.currentTimeMillis() - t0)
320: + " ms).");
321: }
322: });
323:
324: try {
325: fut.get(); // WAIT.
326: } catch (Exception ign) {
327: }
328: }
329:
330: /** Puts the hits in dest.
331: * Collects the "*.class" files named name+".class" and starting with name+"$".
332: */
333: // problem: old private classes are not removed (toplevel private class A defined in B.java !)
334: private static void getClassesAndInnerClassesNamed(File dir,
335: String name, List<File> dest) {
336: File[] files = dir.listFiles(FileUtils.classFileNameFilter); // limit to *.class in dir
337: if (files == null)
338: return;
339: for (File f : files) {
340: if (f.isDirectory())
341: continue;
342:
343: if (f.getName().equalsIgnoreCase(name + ".class"))
344: dest.add(f); // [March2008]: corrected
345: if (f.getName().startsWith(name + "$"))
346: dest.add(f);
347: }
348: }
349:
350: /** if false, we have a kind of "contamination", keeping old removed classes (after renames, removes).
351: * caches classes are then only replaced when they size are different,
352: * this is inexact but ok for a cache used for completion purposes.
353: * Advantage: it's quicker.
354: */
355: private final static boolean deleteCachedClassesAfterRebuild = false;
356:
357: /** Call this on successful build. This allows to reconstruct a valid cache, i.e.
358: * where no duplicate classes may occur. (when moving a class and partial recompile, for example !)
359: * @param rootRebuild null when the whole project was compiled !
360: *
361: * Immediately returns, ENQUEUED.
362: */
363: public Future buildWasSuccessful(final SourceFile rootRebuild) {
364: return analysisExecutionService.submit(new Runnable() {
365: public void run() {
366:
367: MainEditorFrame.debugOut("\r\nSuccessful build for "
368: + rootRebuild);
369:
370: final long t0 = System.currentTimeMillis();
371:
372: // 1) delete actual cache
373: final ProjectSettings actualProject = MainEditorFrame.instance
374: .getActualProject();
375: final File cacheSubRootToConsider;
376: final File classesRootToConsider;
377:
378: if (rootRebuild != null) {
379: String packageToConsider = rootRebuild
380: .getPackageName().replace('.', '/');
381: cacheSubRootToConsider = new File(
382: classesCacheForCompletion,
383: packageToConsider);
384: classesRootToConsider = new File(actualProject
385: .getClasses_Home(), packageToConsider);
386: } else {
387: cacheSubRootToConsider = classesCacheForCompletion;
388: classesRootToConsider = actualProject
389: .getClasses_Home();
390: }
391:
392: if (deleteCachedClassesAfterRebuild) {
393: final List<File> cachedClassesToDelete = new ArrayList<File>();
394: if (rootRebuild != null) {
395: if (rootRebuild.isDirectory()) {
396: // delete all the cached files in that directory
397: //FileUtilities.getAllFilesRecurse_withNameEndingWith(cachedClassesToDelete, cacheSubRootToConsider, ".class");
398: FileUtils.getAllClassFilesRecurse(
399: cacheSubRootToConsider,
400: cachedClassesToDelete, true);
401: } else {
402: // problem: old private classes are not removed (toplevel private class A defined in B.java !)
403: getClassesAndInnerClassesNamed(
404: cacheSubRootToConsider, rootRebuild
405: .getJavaPartName(),
406: cachedClassesToDelete);
407: }
408: } else {
409: // recache all, cause the whole project was successful compiled
410:
411: //FileUtilities.getAllFilesRecurse_withNameEndingWith(cachedClassesToDelete, classesCacheForCompletion, ".class");
412: FileUtils.getAllClassFilesRecurse(
413: classesCacheForCompletion,
414: cachedClassesToDelete, true); // ignore "dots dirs"
415: }
416:
417: long t1 = System.currentTimeMillis();
418:
419: df: for (final File cf : cachedClassesToDelete) {
420: cf.delete();
421: }
422: MainEditorFrame
423: .debugOut("deleting "
424: + cachedClassesToDelete.size()
425: + " cached classes in "
426: + (System.currentTimeMillis() - t1)
427: + " ms");
428: cachedClassesToDelete.clear();
429:
430: // remove empty directories
431: final List<File> allCachedDirs = new ArrayList<File>();
432: FileUtilities.getAllDirectoriesRecurse_depthFirst(
433: allCachedDirs, cacheSubRootToConsider); // depth first is important here
434: //System.out.println("deleting "+allCachedDirs.size()+" cached classes dirs");
435: for (final File d : allCachedDirs) {
436: d.delete();
437: // not so important...
438: }
439: allCachedDirs.clear();
440: }
441:
442: // 2) copy all generated classes to the cache
443: long t1 = System.currentTimeMillis();
444: int baseNameLength = FileUtils.getCanonicalName(
445: actualProject.getClasses_Home()).length();
446: List<File> allClasses = new ArrayList<File>();
447: FileUtils.getAllClassFilesRecurse(
448: classesRootToConsider, allClasses, true);
449: for (final File cf : allClasses) {
450: String baseName = FileUtils.getCanonicalName(cf)
451: .substring(baseNameLength);
452: copyGeneratedClasseInCache(cf, baseName, false); // DO NOT DELETE IT
453: }
454: MainEditorFrame.debugOut("copying " + allClasses.size()
455: + " classes to the cache ("
456: + (System.currentTimeMillis() - t1) + " ms)");
457:
458: MainEditorFrame
459: .debugOut("successful afterbuild done. (tot "
460: + (System.currentTimeMillis() - t0)
461: + " ms)");
462: }
463: });
464: }
465:
466: /** Allows to "keep" the classes for completion purpose,
467: * called when deleting the classes.
468: * Copies only if the length is different (lazy, inexact but ok for our cache purpose).
469: */
470: private void copyGeneratedClasseInCache(final File classFile,
471: final String relativePathName, final boolean deleteSource) {
472: final File dest = new File(this .classesCacheForCompletion,
473: relativePathName);
474: final boolean replace;
475: if (!dest.exists()) {
476: replace = true;
477: } else {
478: //ALWAYS THE CASE... replace = (classFile.lastModified() != dest.lastModified());
479: // CAUTION: not robust, but it's ok because our cache is only
480: // for completion purposes...
481: replace = (classFile.length() != dest.length());
482: }
483:
484: if (replace) {
485: try {
486:
487: if (deleteSource) {
488: if (!classFile.renameTo(dest)) // quick..
489: {
490: FileUtils.copy(classFile, dest);
491: if (!classFile.delete()) {
492: System.out.println("Cannot delete1 "
493: + classFile);
494: }
495: }
496: } else {
497: // just copy
498: FileUtils.copy(classFile, dest);
499: }
500: } catch (Exception e) {
501: e.printStackTrace();
502: }
503:
504: } else if (deleteSource) {
505: // just delete
506: if (!classFile.delete()) {
507: System.out.println("Cannot delete2 " + classFile);
508: }
509: }
510: }
511:
512: /** Call when terminated. And on project refresh.
513: Closes the opened jars and empty the caches.
514: */
515: public void terminate() {
516: MainEditorFrame.debugOut("terminate cfm");
517:
518: classPath.clear();
519: classPathClasses.clear();
520: generatedClasses.clear();
521: generatedClassesCache.clear();
522:
523: for (ZipFile zf : openedZipFiles) {
524: FileUtils.closeIgnoringExceptions(zf);
525: }
526: openedZipFiles.clear();
527:
528: }
529:
530: }
|