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 }