View Javadoc
1   /*
2     Copyright (C) Simuline Inc, Ernst Rei3ner
3   
4     This program is free software; you can redistribute it and/or
5     modify it under the terms of the GNU General Public License
6     as published by the Free Software Foundation; either version 2
7     of the License, or (at your option) any later version.
8   
9     This program is distributed in the hope that it will be useful,
10    but WITHOUT ANY WARRANTY; without even the implied warranty of
11    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12    GNU General Public License for more details.
13  
14    You should have received a copy of the GNU General Public License
15    along with this program; if not, write to the 
16    Free Software Foundation, Inc., 
17    51 Franklin Street, Fifth Floor, 
18    Boston, MA  02110-1301, USA.
19  */
20  
21  package eu.simuline.util;
22  
23  import java.util.List;
24  import java.util.ArrayList;
25  
26  import java.io.File;
27  import java.io.FileInputStream;
28  import java.io.FileOutputStream;
29  import java.io.InputStream;
30  import java.io.FilterInputStream;
31  import java.io.IOException;
32  
33  import java.util.zip.ZipFile;
34  import java.util.zip.ZipEntry;
35  
36  /**
37   * Represents a path to find class and source files on. 
38   *
39   * Created: Wed Jun 14 23:12:06 2006
40   *
41   * @author <a href="mailto:ernst.reissner@simuline.eu">Ernst Reissner</a>
42   * @version 1.0
43   */
44  public final class JavaPath {
45  
46      /* -------------------------------------------------------------------- *
47       * constants.                                                           *
48       * -------------------------------------------------------------------- */
49  
50      /**
51       * The entry of property <code>file.separator</code> as a char. 
52       */
53      private static final char FILE_SEP = 
54  	System.getProperty("file.separator").charAt(0);
55  
56      /**
57       * The entry of property <code>path.separator</code>. 
58       */
59      private static final String PATH_SEP = 
60  	System.getProperty("path.separator");
61  
62      /**
63       * Java's class separator <code>.</code> 
64       * separating classes from their packages 
65       * and also packages from their subpackages. 
66       */
67      private static final char CLASS_SEP = '.';
68  
69      /**
70       * Java's class separator <code>$</code> 
71       * separating inner classes from their enclosing classes. 
72       */
73      private static final String INNER_CLASS_SEP = "\\$";
74  
75      /**
76       * File ending <code>.zip</code> identifying zip-files. 
77       */
78      private static final String ZIP_END = ".zip";
79  
80      /**
81       * File ending <code>.jar</code> identifying jar-files. 
82       */
83      private static final String JAR_END = ".jar";
84  
85      /**
86       * The length of a buffer to read at once. 
87       */
88      private static final int LEN_BUFFER = 1000;
89  
90      /* -------------------------------------------------------------------- *
91       * inner classes.                                                       *
92       * -------------------------------------------------------------------- */
93  
94  
95      /**
96       * Determines whether a class file or a source file is meant. 
97       */
98      public enum ClsSrc {
99  	Class {
100 	    String fileEnding() {
101 		return ".class";
102 	    }
103 	    String trimInnerClass(String clsName) {
104 		return clsName;
105 	    }
106 	}, 
107 	Source {
108 	    String fileEnding() {
109 		return ".java";
110 	    }
111 	    String trimInnerClass(String clsName) {
112 		return clsName.replaceAll(INNER_CLASS_SEP + ".*", "");
113 	    }
114 	};
115 
116 	/**
117 	 * Returns the ending of a class file or of a source file. 
118 	 *
119 	 * @return
120 	 *    either <code>.class</code> or <code>.java</code>. 
121 	 */
122 	abstract String fileEnding();
123 
124 	/**
125 	 * Returns the name of the class enclosing <code>clsName</code> 
126 	 * (including <code>clsName</code> itself) 
127 	 * which has its own source/class-file. 
128 	 *
129 	 * @param clsName
130 	 *    the name of a class as a <code>String</code> value. 
131 	 * @return
132 	 *    The name of the class enclosing <code>clsName</code> 
133 	 *    (including <code>clsName</code> itself) 
134 	 *    which has its own source/class-file. 
135 	 *    Note that for class files, 
136 	 *    this is just <code>clsName</code> itself, 
137 	 *    whereas for source files the part of the classname 
138 	 *    following {@link #INNER_CLASS_SEP} is stripped off. 
139 	 */
140 	abstract String trimInnerClass(String clsName);
141     } // enum ClsSrc 
142 
143     /**
144      * Wrapps a file directly found within a directory 
145      * or within a zip-archive which also includes jar-archives. 
146      * Accordingly, there are two implementations, 
147      * {@link JavaPath.OrdFileWrapper} and {@link JavaPath.ZipEntryWrapper}. 
148      */
149     interface FileWrapper {
150 
151 	/**
152 	 * Returns whether the wrapped file is within a zip-archive. 
153 	 * If this is the case, the file wrapped was created newly. 
154 	 *
155 	 * @return 
156 	 *    a <code>boolean</code> value signifying 
157 	 *    whether the wrapped file is within a zip-archive. 
158 	 */
159 	boolean coversZip();
160 
161 	/**
162 	 * Returns the file wrapped.  
163 	 * If the wrapped file is within a zip-archive, 
164 	 * it was created newly by this <code>FileWrapper</code>. 
165 	 *
166 	 * @return 
167 	 *    the <code>File</code> wrapped. 
168 	 * @throws IOException 
169 	 *    if an error occurs
170 	 */
171 	File getFile() throws IOException;
172 
173 	/**
174 	 * Returns an input stream for the file wrapped.  
175 	 *
176 	 * @return 
177 	 *    the <code>InputStream</code> of the file wrapped. 
178 	 * @throws IOException 
179 	 *    if an error occurs
180 	 */
181 	InputStream getInputStream() throws IOException;
182     } // interface FileWrapper 
183 
184     /**
185      * Represents an ordinary file {@link #file} on a file system. 
186      */
187     static class OrdFileWrapper implements FileWrapper {
188 
189 	private final File file;
190 
191 	OrdFileWrapper(File file) {
192 	    this.file = file;
193 	}
194 
195 	// api-docs inherited from interface FileWrapper 
196 	public boolean coversZip() {
197 	    return false;
198 	}
199 
200 	// api-docs inherited from interface FileWrapper 
201 	public File getFile() {
202 	    return this.file;
203 	}
204 
205 	// api-docs inherited from interface FileWrapper 
206 	public InputStream getInputStream() throws IOException {
207 	    return new FileInputStream(this.file);
208 	}
209     } // class OrdFileWrapper 
210 
211     // **** missing: closing of InputStream: 
212     // both stream and zipfile: zipFile.close();  stream.close();
213     /**
214      * Represents a file {@link #entry} 
215      * which is an entry in a zip-file {@link #zipFile}. 
216      * The zip-file may be in any zip format 
217      * and in particular a jar-archive 
218      * (which is the common application case). 
219      */
220     static class ZipEntryWrapper implements FileWrapper {
221 
222 	/**
223 	 * A filter input stream 
224 	 * closing {@link JavaPath.ZipEntryWrapper#zipFile} 
225 	 * when closing the stream. 
226 	 */
227 	class WrappedInputStream extends FilterInputStream {
228 	    WrappedInputStream(InputStream inputStream) {
229 		super(inputStream);
230 	    }
231 	    public void close() throws IOException {
232 		super.close();
233 		ZipEntryWrapper.this.zipFile.close();
234 	    }
235 	} // class WrappedInputStream 
236 
237 	private final ZipFile zipFile;
238 	private final ZipEntry entry;
239 
240 	ZipEntryWrapper(ZipFile zipFile, ZipEntry entry) {
241 	    this.zipFile = zipFile;
242 	    this.entry = entry;
243 	}
244 
245 	// api-docs inherited from interface FileWrapper 
246 	public boolean coversZip() {
247 	    return true;
248 	}
249 
250 	// api-docs inherited from interface FileWrapper 
251 	public File getFile() throws IOException {
252 	    // by extraction. 
253 	    // may throw IOException
254 	    File ret = File
255 		.createTempFile("JUnitGUI" + System.currentTimeMillis(),
256 				ClsSrc.Source.fileEnding());
257 	    try (
258 		 //new File("/tmp/HI.java"); //this.entry.getName());
259 		 //ret.createNewFile(); 
260 		 // **** does not work well: shall be rec!!!
261 		 // may throw IOException
262 		 InputStream inStream = getInputStream();
263 		 // may throw IOException
264 		 FileOutputStream outStream = new FileOutputStream(ret);
265 		 ) {
266 		    byte[] buf = new byte[LEN_BUFFER];
267 		    int numRead = inStream.read(buf, 0, LEN_BUFFER - 1);
268 		    while (numRead != -1) {
269 			/*     */outStream.write(buf, 0, numRead);
270 			numRead = inStream.read (buf, 0, LEN_BUFFER - 1);
271 		    }
272 	    }
273 
274 	    return ret;
275 	}
276 
277 	// api-docs inherited from interface FileWrapper 
278 	public InputStream getInputStream() throws IOException {
279 	    return this.zipFile.getInputStream(this.entry);
280 	}
281 
282     } // class ZipEntryWrapper 
283 
284 
285     /* -------------------------------------------------------------------- *
286      * fields.                                                              *
287      * -------------------------------------------------------------------- */
288 
289     /**
290      * The list of entries of this path. 
291      * This serves as root path for the source files and class files 
292      * under consideration. 
293      * CAUTION: Note that besides directories 
294      * also <code>.zip</code>-files and  <code>.jar</code>-files are allowed. 
295      */
296     private final List<File> roots;
297 
298     /* -------------------------------------------------------------------- *
299      * constructor.                                                         *
300      * -------------------------------------------------------------------- */
301 
302     /**
303      * Creates a new <code>JavaPath</code> instance.
304      * Essentially, {@link #roots} is initialized. 
305      *
306      * @param path
307      *    a path as a <code>String</code> value. 
308      *    note that the entries of the path must not be the empty string. 
309      * @throws IllegalArgumentException
310      *    if two path separators immediately follow on one another 
311      *    or if they stand at the beginning or at the end 
312      *    of <code>path</code>. 
313      */
314     public JavaPath(String path) {
315 	// split the path along the path separator. 
316 	String[] fileNames = path.split(PATH_SEP);
317 	if (fileNames.length == 0) {
318 	    throw new IllegalArgumentException
319 		("String \"" + path + "\" is not a path. ");
320 	}
321 
322 	this.roots = new ArrayList<File>(fileNames.length);
323 	for (String fileName :fileNames) {
324 	    if (fileName.length() == 0) {
325 		System.out.println
326 		    ("Warning: Found file \"\" in path \"" + path + "\". ");
327 
328 // 		throw new IllegalArgumentException
329 // 		    ("Found file \"\" in path \"" + path + "\". ");
330 	    }
331 // 	for (int i = 0; i < fileNames.length; i++) {
332 // 	    if (fileNames[i].length() == 0) {
333 // 		System.out.println
334 // 		    ("Warning: Found file \"\" in path \"" + path + "\". ");
335 
336 // // 		throw new IllegalArgumentException
337 // // 		    ("Found file \"\" in path \"" + path + "\". ");
338 // 	    }
339 	    this.roots.add(new File(fileName));
340 	}
341     }
342 
343     /* -------------------------------------------------------------------- *
344      * methods.                                                             *
345      * -------------------------------------------------------------------- */
346 
347     /**
348      * Converts a class name into the into the corresponding name 
349      * of a local source file or class file. 
350      *
351      * @param clsName 
352      *    the name of the class as a <code>String</code> value. 
353      * @param clsSrc 
354      *    a <code>ClsSrc</code> which determines 
355      *    whether to convert the class name 
356      *    into a class file or into a source file. 
357      * @return 
358      *    the name of the local class or source file 
359      *    of the given class. 
360      *    Note that this is no absolute pathname of course: 
361      *    {@link #roots} is not read. 
362      */
363     private String cls2locFile(String clsName, ClsSrc clsSrc) {
364 /*
365 	if (cls.isArray()) {
366 	    throw new IllegalArgumentException
367 		("Tried to load array " + cls + " by path. ");
368 	}
369 	if (cls.isPrimitive()) {
370 	    throw new IllegalArgumentException
371 		("Tried to load primitive class " + cls + " by path. ");
372 	}
373 */
374 	// replace file separator / by .
375 	String localFilename = clsName.replace(CLASS_SEP, FILE_SEP);
376 	// for source files: remove names of inner classes 
377 	localFilename = clsSrc.trimInnerClass(localFilename);
378 	// append the appropriate file ending 
379 	StringBuffer fileNameBuf = new StringBuffer(localFilename);
380 	fileNameBuf.append(clsSrc.fileEnding());
381 	return fileNameBuf.toString();
382     }
383 
384     /**
385      * Converts a class name into the corresponding source file or class file 
386      * if possible. 
387      *
388      * @param clsName 
389      *    the name of the class as a <code>String</code> value. 
390      * @param clsSrc 
391      *    a <code>ClsSrc</code> which determines 
392      *    whether to convert the class name 
393      *    into a class file or into a source file. 
394      * @return 
395      *    the source or class <code>File</code> 
396      *    corresponding with the given classname if there is one; 
397      *    otherwise returns <code>null</code>. 
398      *    Note that if the file is found within a zip- or jar-file, 
399      *    a temporal file is created. 
400      *    In any case, the file returned exists 
401      *    unless <code>null</code> is returned. 
402      */
403     public File getFile(String clsName, ClsSrc clsSrc) {
404 	return getFile(cls2locFile(clsName, clsSrc));
405     }
406 
407     // may return null 
408     public File getFile(String localFilename) {
409 	FileWrapper fileWr = locFile2Wrapper(localFilename);
410 	
411 	try {
412 	    return fileWr == null ? null : fileWr.getFile();
413 	} catch (IOException ioe) {
414 	    return null;
415 	}
416     }
417 
418     public InputStream getInputStream(String clsName) throws IOException {
419 	FileWrapper fileWrapper = locFile2Wrapper(cls2locFile(clsName,
420 							      ClsSrc.Class));
421 	if (fileWrapper == null) {
422 	    return null;
423 	}
424 
425 	return fileWrapper.getInputStream();
426     }
427 
428 
429     /**
430      * Converts a local file name into the wrapper 
431      * of the file found on the path. 
432      * Note that also within zip- or jar-file is searched. 
433      *
434      * @param localFilename 
435      *    the name of a local file as a <code>String</code> value. 
436      * @return 
437      *    a <code>FileWrapper</code> representing the first file 
438      *    found on the path with the appropriate name. 
439      *    If the file is found in a directory of the path, 
440      *    an {@link OrdFileWrapper JavaPath.OrdFileWrapper} is returned, 
441      *    if it is found within a zip- or jar-file, 
442      *    a {@link ZipEntryWrapper JavaPath.ZipEntryWrapper} is returned. 
443      */
444     private FileWrapper locFile2Wrapper(String localFilename) {
445 	
446 	File cand = null;
447 	// otherwise: might not be initialized. 
448 	// this is impossible because this.files is not empty. 
449 
450 	// search for the file with the given name along the path. 
451 	for (File candParent : this.roots) {
452 	    if (candParent.isDirectory()) {
453 		// find directly in the directory candParent
454 		cand = new File(candParent, localFilename);
455 		if (cand.exists() && cand.isFile()) {
456 		    return new OrdFileWrapper(cand);
457 		}
458 	    } else {
459 		// find directly in the zip- or jar-file candParent
460 		assert !candParent.isDirectory();
461 		if (!(candParent.getName().endsWith(ZIP_END) || 
462 		      candParent.getName().endsWith(JAR_END))) {
463 		    continue;
464 		}
465 		// here, we have a zip-file at least 
466 		// if not a jar-file. 
467 		ZipFile zipFile;
468 		try {
469 		    zipFile = new ZipFile(candParent);
470 		} catch (IOException  e) {
471 		    continue;
472 		}
473 		// Here, the zip-file is readable. 
474 		ZipEntry entry = zipFile.getEntry(localFilename);
475 		if (entry == null) {
476 		    // file not found 
477 		    continue;
478 		}
479 		return new ZipEntryWrapper(zipFile, entry);
480 	    }
481 	}
482 	return null;
483     }
484 
485 
486     // **** no support for zip- and jar-files. 
487     // returns null if not found on the path. 
488     public String getLocFileName(File absFile) {
489 	String fileName = absFile.getPath();
490 	for (File cand : this.roots) {
491 
492 	    if (fileName.startsWith(cand.getPath())) {
493 		fileName = fileName.substring(cand.getPath().length() + 1,
494 					      fileName      .length());
495 		return fileName;
496 	    }
497 	}
498 	// Here, the file was not found on the path 
499 	return null;
500     }
501 
502     public String locFile2cls(String locFileName, ClsSrc clsSrc) {
503 	if (!locFileName.endsWith(clsSrc.fileEnding())) {
504 	    throw new IllegalArgumentException
505 		("Expected filename with ending \"" + clsSrc.fileEnding() 
506 		 + "\" but found \"" + locFileName + "\". ");
507 	}
508 	locFileName = locFileName.substring(0,
509 					    /*    */locFileName.length() - 
510 					    clsSrc.fileEnding().length());
511 	 locFileName = locFileName.replace(FILE_SEP, CLASS_SEP);
512 	 return locFileName;
513     }
514 
515     public String absFile2cls(File absFile, ClsSrc clsSrc) {
516 	String locFileName = getLocFileName(absFile);
517 System.out.println("locFileName: " + locFileName);
518 	
519 	if (locFileName == null) {
520 	    return null;
521 	}
522 	return locFile2cls(locFileName, clsSrc);
523     }
524 
525     public String toString() {
526 	StringBuffer ret = new StringBuffer();
527 	ret.append("<JavaPath>");
528 	ret.append(this.roots);
529 	ret.append("</JavaPath>");
530 	return ret.toString();
531     }
532 
533     public static void main(String[] args) {
534 	//System.out.println("tt: "+new JavaPath(":"));
535 	//System.out.println("tt: "+new JavaPath(""));
536 	System.out.println("tt: " + new JavaPath("/home/ernst"));
537 	System.out.println("tt: " + new JavaPath("/home/ernst:/usr/bin"));
538 
539 	//JavaPath jPath = new JavaPath("/home/ernst/.../src/");
540 /*
541 	System.out.println("tt: "+
542 			   jPath.getFile(AddArrays.class, ClsSrc.Class));
543 	System.out.println("tt: "+
544 			   jPath.getFile(AddArrays.class, ClsSrc.Source));
545 
546 */
547     }
548 }