1 /*******************************************************************************
2  * Copyright (c) 2006, 2014 IBM Corporation and others.
3  * All rights reserved. This program and the accompanying materials
4  * are made available under the terms of the Eclipse Public License v1.0
5  * which accompanies this distribution, and is available at
6  * http://www.eclipse.org/legal/epl-v10.html
7  *
8  * Contributors:
9  *     IBM Corporation - initial API and implementation
10  *******************************************************************************/
11 package org.eclipse.jdt.internal.compiler.apt.util;
12 
13 import java.io.File;
14 import java.io.IOException;
15 import java.net.MalformedURLException;
16 import java.net.URI;
17 import java.net.URISyntaxException;
18 import java.net.URL;
19 import java.net.URLClassLoader;
20 import java.nio.charset.Charset;
21 import java.text.MessageFormat;
22 import java.util.ArrayList;
23 import java.util.Arrays;
24 import java.util.HashMap;
25 import java.util.Iterator;
26 import java.util.Locale;
27 import java.util.Map;
28 import java.util.MissingResourceException;
29 import java.util.ResourceBundle;
30 import java.util.Set;
31 import java.util.StringTokenizer;
32 import java.util.zip.ZipException;
33 
34 import javax.tools.FileObject;
35 import javax.tools.JavaFileObject;
36 import javax.tools.StandardJavaFileManager;
37 import javax.tools.StandardLocation;
38 import javax.tools.JavaFileObject.Kind;
39 
40 import org.eclipse.jdt.core.compiler.IProblem;
41 import org.eclipse.jdt.internal.compiler.batch.FileSystem;
42 import org.eclipse.jdt.internal.compiler.batch.Main;
43 import org.eclipse.jdt.internal.compiler.batch.Main.ResourceBundleFactory;
44 import org.eclipse.jdt.internal.compiler.classfmt.ClassFileConstants;
45 import org.eclipse.jdt.internal.compiler.env.AccessRestriction;
46 import org.eclipse.jdt.internal.compiler.env.AccessRule;
47 import org.eclipse.jdt.internal.compiler.env.AccessRuleSet;
48 import org.eclipse.jdt.internal.compiler.impl.CompilerOptions;
49 
50 /**
51  * Implementation of the Standard Java File Manager
52  */
53 public class EclipseFileManager implements StandardJavaFileManager {
54 	private static final String NO_EXTENSION = "";//$NON-NLS-1$
55 	static final int HAS_EXT_DIRS = 1;
56 	static final int HAS_BOOTCLASSPATH = 2;
57 	static final int HAS_ENDORSED_DIRS = 4;
58 	static final int HAS_PROCESSORPATH = 8;
59 
60 	Map<File, Archive> archivesCache;
61 	Charset charset;
62 	Locale locale;
63 	Map<String, Iterable<? extends File>> locations;
64 	int flags;
65 	public ResourceBundle bundle;
66 
EclipseFileManager(Locale locale, Charset charset)67 	public EclipseFileManager(Locale locale, Charset charset) {
68 		this.locale = locale == null ? Locale.getDefault() : locale;
69 		this.charset = charset == null ? Charset.defaultCharset() : charset;
70 		this.locations = new HashMap<String, Iterable<? extends File>>();
71 		this.archivesCache = new HashMap<File, Archive>();
72 		try {
73 			this.setLocation(StandardLocation.PLATFORM_CLASS_PATH, getDefaultBootclasspath());
74 			Iterable<? extends File> defaultClasspath = getDefaultClasspath();
75 			this.setLocation(StandardLocation.CLASS_PATH, defaultClasspath);
76 			this.setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, defaultClasspath);
77 		} catch (IOException e) {
78 			// ignore
79 		}
80 		try {
81 			this.bundle = ResourceBundleFactory.getBundle(this.locale);
82 		} catch(MissingResourceException e) {
83 			System.out.println("Missing resource : " + Main.bundleName.replace('.', '/') + ".properties for locale " + locale); //$NON-NLS-1$//$NON-NLS-2$
84 		}
85 	}
86 
addFiles(File[][] jars, ArrayList<File> files)87 	private void addFiles(File[][] jars, ArrayList<File> files) {
88 		if (jars != null) {
89 			for (File[] currentJars : jars) {
90 				if (currentJars != null) {
91 					for (File currentJar : currentJars) {
92 						if (currentJar.exists()) {
93 							files.add(currentJar);
94 						}
95 					}
96 				}
97 			}
98 		}
99 	}
100 
101 
addFilesFrom(File javaHome, String propertyName, String defaultPath, ArrayList<File> files)102 	private void addFilesFrom(File javaHome, String propertyName, String defaultPath, ArrayList<File> files) {
103 		String extdirsStr = System.getProperty(propertyName);
104 		File[] directoriesToCheck = null;
105 		if (extdirsStr == null) {
106 			if (javaHome != null) {
107 				directoriesToCheck = new File[] { new File(javaHome, defaultPath) };
108 			}
109 		} else {
110 			StringTokenizer tokenizer = new StringTokenizer(extdirsStr, File.pathSeparator);
111 			ArrayList<String> paths = new ArrayList<String>();
112 			while (tokenizer.hasMoreTokens()) {
113 				paths.add(tokenizer.nextToken());
114 			}
115 			if (paths.size() != 0) {
116 				directoriesToCheck = new File[paths.size()];
117 				for (int i = 0; i < directoriesToCheck.length; i++)  {
118 					directoriesToCheck[i] = new File(paths.get(i));
119 				}
120 			}
121 		}
122 		if (directoriesToCheck != null) {
123 			addFiles(Main.getLibrariesFiles(directoriesToCheck), files);
124 		}
125 
126 	}
127 
128 	/* (non-Javadoc)
129 	 * @see javax.tools.JavaFileManager#close()
130 	 */
close()131 	public void close() throws IOException {
132 		this.locations = null;
133 		for (Archive archive : this.archivesCache.values()) {
134 			archive.close();
135 		}
136 	}
137 
collectAllMatchingFiles(File file, String normalizedPackageName, Set<Kind> kinds, boolean recurse, ArrayList<JavaFileObject> collector)138 	private void collectAllMatchingFiles(File file, String normalizedPackageName, Set<Kind> kinds, boolean recurse, ArrayList<JavaFileObject> collector) {
139 		if (!isArchive(file)) {
140 			// we must have a directory
141 			File currentFile = new File(file, normalizedPackageName);
142 			if (!currentFile.exists()) return;
143 			String path;
144 			try {
145 				path = currentFile.getCanonicalPath();
146 			} catch (IOException e) {
147 				return;
148 			}
149 			if (File.separatorChar == '/') {
150 				if (!path.endsWith(normalizedPackageName)) return;
151 			} else if (!path.endsWith(normalizedPackageName.replace('/', File.separatorChar))) return;
152 			File[] files = currentFile.listFiles();
153 			if (files != null) {
154 				// this was a directory
155 				for (File f : files) {
156 					if (f.isDirectory() && recurse) {
157 						collectAllMatchingFiles(file, normalizedPackageName + '/' + f.getName(), kinds, recurse, collector);
158 					} else {
159 						final Kind kind = getKind(f);
160 						if (kinds.contains(kind)) {
161 							collector.add(new EclipseFileObject(normalizedPackageName + f.getName(), f.toURI(), kind, this.charset));
162 						}
163 					}
164 				}
165 			}
166 		} else {
167 			Archive archive = this.getArchive(file);
168 			String key = normalizedPackageName;
169 			if (!normalizedPackageName.endsWith("/")) {//$NON-NLS-1$
170 				key += '/';
171 			}
172 			// we have an archive file
173 			if (recurse) {
174 				for (String packageName : archive.allPackages()) {
175 					if (packageName.startsWith(key)) {
176 						ArrayList<String> types = archive.getTypes(packageName);
177 						if (types != null) {
178 							for (String typeName : types) {
179 								final Kind kind = getKind(getExtension(typeName));
180 								if (kinds.contains(kind)) {
181 									collector.add(archive.getArchiveFileObject(packageName + typeName, this.charset));
182 								}
183 							}
184 						}
185 					}
186 				}
187 			} else {
188 				ArrayList<String> types = archive.getTypes(key);
189 				if (types != null) {
190 					for (String typeName : types) {
191 						final Kind kind = getKind(typeName);
192 						if (kinds.contains(kind)) {
193 							collector.add(archive.getArchiveFileObject(normalizedPackageName + typeName, this.charset));
194 						}
195 					}
196 				}
197 			}
198 		}
199 	}
200 
concatFiles(Iterable<? extends File> iterable, Iterable<? extends File> iterable2)201 	private Iterable<? extends File> concatFiles(Iterable<? extends File> iterable, Iterable<? extends File> iterable2) {
202 		ArrayList<File> list = new ArrayList<File>();
203 		if (iterable2 == null) return iterable;
204 		for (Iterator<? extends File> iterator = iterable.iterator(); iterator.hasNext(); ) {
205 			list.add(iterator.next());
206 		}
207 		for (Iterator<? extends File> iterator = iterable2.iterator(); iterator.hasNext(); ) {
208 			list.add(iterator.next());
209 		}
210 		return list;
211 	}
212 
213 	/* (non-Javadoc)
214 	 * @see javax.tools.JavaFileManager#flush()
215 	 */
flush()216 	public void flush() throws IOException {
217 		for (Archive archive : this.archivesCache.values()) {
218 			archive.flush();
219 		}
220 	}
221 
getArchive(File f)222 	private Archive getArchive(File f) {
223 		// check the archive (jar/zip) cache
224 		Archive archive = this.archivesCache.get(f);
225 		if (archive == null) {
226 			// create a new archive
227 			if (f.exists()) {
228     			try {
229     				archive = new Archive(f);
230     			} catch (ZipException e) {
231     				// ignore
232     			} catch (IOException e) {
233     				// ignore
234     			}
235     			if (archive != null) {
236     				this.archivesCache.put(f, archive);
237     			} else {
238     				this.archivesCache.put(f, Archive.UNKNOWN_ARCHIVE);
239     			}
240 			} else {
241 				this.archivesCache.put(f, Archive.UNKNOWN_ARCHIVE);
242 			}
243 		}
244 		return archive;
245 	}
246 
247 	/* (non-Javadoc)
248 	 * @see javax.tools.JavaFileManager#getClassLoader(javax.tools.JavaFileManager.Location)
249 	 */
getClassLoader(Location location)250 	public ClassLoader getClassLoader(Location location) {
251 		Iterable<? extends File> files = getLocation(location);
252 		if (files == null) {
253 			// location is unknown
254 			return null;
255 		}
256 		ArrayList<URL> allURLs = new ArrayList<URL>();
257 		for (File f : files) {
258 			try {
259 				allURLs.add(f.toURI().toURL());
260 			} catch (MalformedURLException e) {
261 				// the url is malformed - this should not happen
262 				throw new RuntimeException(e);
263 			}
264 		}
265 		URL[] result = new URL[allURLs.size()];
266 		return new URLClassLoader(allURLs.toArray(result), getClass().getClassLoader());
267 	}
268 
getPathsFrom(String path)269 	private Iterable<? extends File> getPathsFrom(String path) {
270 		ArrayList<FileSystem.Classpath> paths = new ArrayList<FileSystem.Classpath>();
271 		ArrayList<File> files = new ArrayList<File>();
272 		try {
273 			this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false);
274 		} catch (IllegalArgumentException e) {
275 			return null;
276 		}
277 		for (FileSystem.Classpath classpath : paths) {
278 			files.add(new File(classpath.getPath()));
279 		}
280 		return files;
281 	}
282 
getDefaultBootclasspath()283 	Iterable<? extends File> getDefaultBootclasspath() {
284 		ArrayList<File> files = new ArrayList<File>();
285 		String javaversion = System.getProperty("java.version");//$NON-NLS-1$
286 		if(javaversion.length() > 3)
287 			javaversion = javaversion.substring(0, 3);
288 		long jdkLevel = CompilerOptions.versionToJdkLevel(javaversion);
289 		if (jdkLevel < ClassFileConstants.JDK1_6) {
290 			// wrong jdk - 1.6 or above is required
291 			return null;
292 		}
293 
294 		/*
295 		 * Handle >= JDK 1.6
296 		 */
297 		String javaHome = System.getProperty("java.home"); //$NON-NLS-1$
298 		File javaHomeFile = null;
299 		if (javaHome != null) {
300 			javaHomeFile = new File(javaHome);
301 			if (!javaHomeFile.exists())
302 				javaHomeFile = null;
303 		}
304 
305 		addFilesFrom(javaHomeFile, "java.endorsed.dirs", "/lib/endorsed", files);//$NON-NLS-1$//$NON-NLS-2$
306 		if (javaHomeFile != null) {
307 			File[] directoriesToCheck = null;
308 			if (System.getProperty("os.name").startsWith("Mac")) {//$NON-NLS-1$//$NON-NLS-2$
309 				directoriesToCheck = new File[] { new File(javaHomeFile, "../Classes"), //$NON-NLS-1$
310 				};
311 			} else {
312 				directoriesToCheck = new File[] { new File(javaHomeFile, "lib") //$NON-NLS-1$
313 				};
314 			}
315 			File[][] jars = Main.getLibrariesFiles(directoriesToCheck);
316 			addFiles(jars, files);
317 		}
318 		addFilesFrom(javaHomeFile, "java.ext.dirs", "/lib/ext", files);//$NON-NLS-1$//$NON-NLS-2$
319 		return files;
320 	}
321 
getDefaultClasspath()322 	Iterable<? extends File> getDefaultClasspath() {
323 		// default classpath
324 		ArrayList<File> files = new ArrayList<File>();
325 		String classProp = System.getProperty("java.class.path"); //$NON-NLS-1$
326 		if ((classProp == null) || (classProp.length() == 0)) {
327 			return null;
328 		} else {
329 			StringTokenizer tokenizer = new StringTokenizer(classProp, File.pathSeparator);
330 			String token;
331 			while (tokenizer.hasMoreTokens()) {
332 				token = tokenizer.nextToken();
333 				File file = new File(token);
334 				if (file.exists()) {
335 					files.add(file);
336 				}
337 			}
338 		}
339 		return files;
340 	}
341 
getEndorsedDirsFrom(String path)342 	private Iterable<? extends File> getEndorsedDirsFrom(String path) {
343 		ArrayList<FileSystem.Classpath> paths = new ArrayList<FileSystem.Classpath>();
344 		ArrayList<File> files = new ArrayList<File>();
345 		try {
346 			this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false);
347 		} catch (IllegalArgumentException e) {
348 			return null;
349 		}
350 		for (FileSystem.Classpath classpath : paths) {
351 			files.add(new File(classpath.getPath()));
352 		}
353 		return files;
354 	}
355 
getExtdirsFrom(String path)356 	private Iterable<? extends File> getExtdirsFrom(String path) {
357 		ArrayList<FileSystem.Classpath> paths = new ArrayList<FileSystem.Classpath>();
358 		ArrayList<File> files = new ArrayList<File>();
359 		try {
360 			this.processPathEntries(Main.DEFAULT_SIZE_CLASSPATH, paths, path, this.charset.name(), false, false);
361 		} catch (IllegalArgumentException e) {
362 			return null;
363 		}
364 		for (FileSystem.Classpath classpath : paths) {
365 			files.add(new File(classpath.getPath()));
366 		}
367 		return files;
368 	}
369 
getExtension(File file)370 	private String getExtension(File file) {
371 		String name = file.getName();
372 		return getExtension(name);
373 	}
getExtension(String name)374 	private String getExtension(String name) {
375 		int index = name.lastIndexOf('.');
376 		if (index == -1) {
377 			return EclipseFileManager.NO_EXTENSION;
378 		}
379 		return name.substring(index);
380 	}
381 
382 	/* (non-Javadoc)
383 	 * @see javax.tools.JavaFileManager#getFileForInput(javax.tools.JavaFileManager.Location, java.lang.String, java.lang.String)
384 	 */
getFileForInput(Location location, String packageName, String relativeName)385 	public FileObject getFileForInput(Location location, String packageName, String relativeName) throws IOException {
386 		Iterable<? extends File> files = getLocation(location);
387 		if (files == null) {
388 			throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$
389 		}
390 		String normalizedFileName = normalized(packageName) + '/' + relativeName.replace('\\', '/');
391 		for (File file : files) {
392 			if (file.isDirectory()) {
393 				// handle directory
394 				File f = new File(file, normalizedFileName);
395 				if (f.exists()) {
396 					return new EclipseFileObject(packageName + File.separator + relativeName, f.toURI(), getKind(f), this.charset);
397 				} else {
398 					continue; // go to next entry in the location
399 				}
400 			} else if (isArchive(file)) {
401 				// handle archive file
402 				Archive archive = getArchive(file);
403 				if (archive != Archive.UNKNOWN_ARCHIVE) {
404 					if (archive.contains(normalizedFileName)) {
405 						return archive.getArchiveFileObject(normalizedFileName, this.charset);
406 					}
407 				}
408 			}
409 		}
410 		return null;
411 	}
412 
413 	/* (non-Javadoc)
414 	 * @see javax.tools.JavaFileManager#getFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, java.lang.String, javax.tools.FileObject)
415 	 */
getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)416 	public FileObject getFileForOutput(Location location, String packageName, String relativeName, FileObject sibling)
417 			throws IOException {
418 		Iterable<? extends File> files = getLocation(location);
419 		if (files == null) {
420 			throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$
421 		}
422 		final Iterator<? extends File> iterator = files.iterator();
423 		if (iterator.hasNext()) {
424 			File file = iterator.next();
425 			String normalizedFileName = normalized(packageName) + '/' + relativeName.replace('\\', '/');
426 			File f = new File(file, normalizedFileName);
427 			return new EclipseFileObject(packageName + File.separator + relativeName, f.toURI(), getKind(f), this.charset);
428 		} else {
429 			throw new IllegalArgumentException("location is empty : " + location);//$NON-NLS-1$
430 		}
431 	}
432 
433 	/* (non-Javadoc)
434 	 * @see javax.tools.JavaFileManager#getJavaFileForInput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind)
435 	 */
getJavaFileForInput(Location location, String className, Kind kind)436 	public JavaFileObject getJavaFileForInput(Location location, String className, Kind kind) throws IOException {
437 		if (kind != Kind.CLASS && kind != Kind.SOURCE) {
438 			throw new IllegalArgumentException("Invalid kind : " + kind);//$NON-NLS-1$
439 		}
440 		Iterable<? extends File> files = getLocation(location);
441 		if (files == null) {
442 			throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$
443 		}
444 		String normalizedFileName = normalized(className);
445 		normalizedFileName += kind.extension;
446 		for (File file : files) {
447 			if (file.isDirectory()) {
448 				// handle directory
449 				File f = new File(file, normalizedFileName);
450 				if (f.exists()) {
451 					return new EclipseFileObject(className, f.toURI(), kind, this.charset);
452 				} else {
453 					continue; // go to next entry in the location
454 				}
455 			} else if (isArchive(file)) {
456 				// handle archive file
457 				Archive archive = getArchive(file);
458 				if (archive != Archive.UNKNOWN_ARCHIVE) {
459 					if (archive.contains(normalizedFileName)) {
460 						return archive.getArchiveFileObject(normalizedFileName, this.charset);
461 					}
462 				}
463 			}
464 		}
465 		return null;
466 	}
467 
468 	/* (non-Javadoc)
469 	 * @see javax.tools.JavaFileManager#getJavaFileForOutput(javax.tools.JavaFileManager.Location, java.lang.String, javax.tools.JavaFileObject.Kind, javax.tools.FileObject)
470 	 */
getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)471 	public JavaFileObject getJavaFileForOutput(Location location, String className, Kind kind, FileObject sibling)
472 			throws IOException {
473 		if (kind != Kind.CLASS && kind != Kind.SOURCE) {
474 			throw new IllegalArgumentException("Invalid kind : " + kind);//$NON-NLS-1$
475 		}
476 		Iterable<? extends File> files = getLocation(location);
477 		if (files == null) {
478 			if (!location.equals(StandardLocation.CLASS_OUTPUT)
479 					&& !location.equals(StandardLocation.SOURCE_OUTPUT))
480 				throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$
481 			// we will use either the sibling or user.dir
482 			if (sibling != null) {
483 				String normalizedFileName = normalized(className);
484 				int index = normalizedFileName.lastIndexOf('/');
485 				if (index != -1) {
486 					normalizedFileName = normalizedFileName.substring(index + 1);
487 				}
488 				normalizedFileName += kind.extension;
489 				URI uri = sibling.toUri();
490 				URI uri2 = null;
491 				try {
492 					String path = uri.getPath();
493 					index = path.lastIndexOf('/');
494 					if (index != -1) {
495 						path = path.substring(0, index + 1);
496 						path += normalizedFileName;
497 					}
498 					uri2 = new URI(uri.getScheme(), uri.getHost(), path, uri.getFragment());
499 				} catch (URISyntaxException e) {
500 					throw new IllegalArgumentException("invalid sibling");//$NON-NLS-1$
501 				}
502 				return new EclipseFileObject(className, uri2, kind, this.charset);
503 			} else {
504 				String normalizedFileName = normalized(className);
505 				normalizedFileName += kind.extension;
506 				File f = new File(System.getProperty("user.dir"), normalizedFileName);//$NON-NLS-1$
507 				return new EclipseFileObject(className, f.toURI(), kind, this.charset);
508 			}
509 		}
510 		final Iterator<? extends File> iterator = files.iterator();
511 		if (iterator.hasNext()) {
512 			File file = iterator.next();
513 			String normalizedFileName = normalized(className);
514 			normalizedFileName += kind.extension;
515 			File f = new File(file, normalizedFileName);
516 			return new EclipseFileObject(className, f.toURI(), kind, this.charset);
517 		} else {
518 			throw new IllegalArgumentException("location is empty : " + location);//$NON-NLS-1$
519 		}
520 	}
521 
522 	/* (non-Javadoc)
523 	 * @see javax.tools.StandardJavaFileManager#getJavaFileObjects(java.io.File[])
524 	 */
getJavaFileObjects(File... files)525 	public Iterable<? extends JavaFileObject> getJavaFileObjects(File... files) {
526 		return getJavaFileObjectsFromFiles(Arrays.asList(files));
527 	}
528 
529 	/* (non-Javadoc)
530 	 * @see javax.tools.StandardJavaFileManager#getJavaFileObjects(java.lang.String[])
531 	 */
getJavaFileObjects(String... names)532 	public Iterable<? extends JavaFileObject> getJavaFileObjects(String... names) {
533 		return getJavaFileObjectsFromStrings(Arrays.asList(names));
534 	}
535 
536 	/* (non-Javadoc)
537 	 * @see javax.tools.StandardJavaFileManager#getJavaFileObjectsFromFiles(java.lang.Iterable)
538 	 */
getJavaFileObjectsFromFiles(Iterable<? extends File> files)539 	public Iterable<? extends JavaFileObject> getJavaFileObjectsFromFiles(Iterable<? extends File> files) {
540 		ArrayList<JavaFileObject> javaFileArrayList = new ArrayList<JavaFileObject>();
541 		for (File f : files) {
542 			if (f.isDirectory()) {
543 				throw new IllegalArgumentException("file : " + f.getAbsolutePath() + " is a directory"); //$NON-NLS-1$ //$NON-NLS-2$
544 			}
545 			javaFileArrayList.add(new EclipseFileObject(f.getAbsolutePath(), f.toURI(), getKind(f), this.charset));
546 		}
547 		return javaFileArrayList;
548 	}
549 
550 	/* (non-Javadoc)
551 	 * @see javax.tools.StandardJavaFileManager#getJavaFileObjectsFromStrings(java.lang.Iterable)
552 	 */
getJavaFileObjectsFromStrings(Iterable<String> names)553 	public Iterable<? extends JavaFileObject> getJavaFileObjectsFromStrings(Iterable<String> names) {
554 		ArrayList<File> files = new ArrayList<File>();
555 		for (String name : names) {
556 			files.add(new File(name));
557 		}
558 		return getJavaFileObjectsFromFiles(files);
559 	}
560 
getKind(File f)561 	public Kind getKind(File f) {
562 		return getKind(getExtension(f));
563 	}
564 
getKind(String extension)565 	private Kind getKind(String extension) {
566 		if (Kind.CLASS.extension.equals(extension)) {
567 			return Kind.CLASS;
568 		} else if (Kind.SOURCE.extension.equals(extension)) {
569 			return Kind.SOURCE;
570 		} else if (Kind.HTML.extension.equals(extension)) {
571 			return Kind.HTML;
572 		}
573 		return Kind.OTHER;
574 	}
575 
576 	/* (non-Javadoc)
577 	 * @see javax.tools.StandardJavaFileManager#getLocation(javax.tools.JavaFileManager.Location)
578 	 */
getLocation(Location location)579 	public Iterable<? extends File> getLocation(Location location) {
580 		if (this.locations == null) return null;
581 		return this.locations.get(location.getName());
582 	}
583 
getOutputDir(String string)584 	private Iterable<? extends File> getOutputDir(String string) {
585 		if ("none".equals(string)) {//$NON-NLS-1$
586 			return null;
587 		}
588 		File file = new File(string);
589 		if (file.exists() && !file.isDirectory()) {
590 			throw new IllegalArgumentException("file : " + file.getAbsolutePath() + " is not a directory");//$NON-NLS-1$//$NON-NLS-2$
591 		}
592 		ArrayList<File> list = new ArrayList<File>(1);
593 		list.add(file);
594 		return list;
595 	}
596 
597 	/* (non-Javadoc)
598 	 * @see javax.tools.JavaFileManager#handleOption(java.lang.String, java.util.Iterator)
599 	 */
handleOption(String current, Iterator<String> remaining)600 	public boolean handleOption(String current, Iterator<String> remaining) {
601 		try {
602 			if ("-bootclasspath".equals(current)) {//$NON-NLS-1$
603 				remaining.remove(); // remove the current option
604 				if (remaining.hasNext()) {
605 					final Iterable<? extends File> bootclasspaths = getPathsFrom(remaining.next());
606 					if (bootclasspaths != null) {
607 						Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH);
608 						if ((this.flags & HAS_ENDORSED_DIRS) == 0
609 								&& (this.flags & HAS_EXT_DIRS) == 0) {
610 							// override default bootclasspath
611 							setLocation(StandardLocation.PLATFORM_CLASS_PATH, bootclasspaths);
612 						} else if ((this.flags & HAS_ENDORSED_DIRS) != 0) {
613 							// endorseddirs have been processed first
614 							setLocation(StandardLocation.PLATFORM_CLASS_PATH,
615 									concatFiles(iterable, bootclasspaths));
616 						} else {
617 							// extdirs have been processed first
618 							setLocation(StandardLocation.PLATFORM_CLASS_PATH,
619 									prependFiles(iterable, bootclasspaths));
620 						}
621 					}
622 					remaining.remove();
623 					this.flags |= HAS_BOOTCLASSPATH;
624 					return true;
625 				} else {
626 					throw new IllegalArgumentException();
627 				}
628 			}
629 			if ("-classpath".equals(current) || "-cp".equals(current)) {//$NON-NLS-1$//$NON-NLS-2$
630 				remaining.remove(); // remove the current option
631 				if (remaining.hasNext()) {
632 					final Iterable<? extends File> classpaths = getPathsFrom(remaining.next());
633 					if (classpaths != null) {
634 						Iterable<? extends File> iterable = getLocation(StandardLocation.CLASS_PATH);
635 						if (iterable != null) {
636 							setLocation(StandardLocation.CLASS_PATH,
637 								concatFiles(iterable, classpaths));
638 						} else {
639 							setLocation(StandardLocation.CLASS_PATH, classpaths);
640 						}
641 						if ((this.flags & HAS_PROCESSORPATH) == 0) {
642 							setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, classpaths);
643 						}
644 					}
645 					remaining.remove();
646 					return true;
647 				} else {
648 					throw new IllegalArgumentException();
649 				}
650 			}
651 			if ("-encoding".equals(current)) {//$NON-NLS-1$
652 				remaining.remove(); // remove the current option
653 				if (remaining.hasNext()) {
654 					this.charset = Charset.forName(remaining.next());
655 					remaining.remove();
656 					return true;
657 				} else {
658 					throw new IllegalArgumentException();
659 				}
660 			}
661 			if ("-sourcepath".equals(current)) {//$NON-NLS-1$
662 				remaining.remove(); // remove the current option
663 				if (remaining.hasNext()) {
664 					final Iterable<? extends File> sourcepaths = getPathsFrom(remaining.next());
665 					if (sourcepaths != null) setLocation(StandardLocation.SOURCE_PATH, sourcepaths);
666 					remaining.remove();
667 					return true;
668 				} else {
669 					throw new IllegalArgumentException();
670 				}
671 			}
672 			if ("-extdirs".equals(current)) {//$NON-NLS-1$
673 				remaining.remove(); // remove the current option
674 				if (remaining.hasNext()) {
675 					Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH);
676 					setLocation(StandardLocation.PLATFORM_CLASS_PATH,
677 							concatFiles(iterable, getExtdirsFrom(remaining.next())));
678 					remaining.remove();
679 					this.flags |= HAS_EXT_DIRS;
680 					return true;
681 				} else {
682 					throw new IllegalArgumentException();
683 				}
684 			}
685 			if ("-endorseddirs".equals(current)) {//$NON-NLS-1$
686 				remaining.remove(); // remove the current option
687 				if (remaining.hasNext()) {
688 					Iterable<? extends File> iterable = getLocation(StandardLocation.PLATFORM_CLASS_PATH);
689 					setLocation(StandardLocation.PLATFORM_CLASS_PATH,
690 							prependFiles(iterable, getEndorsedDirsFrom(remaining.next())));
691 					remaining.remove();
692 					this.flags |= HAS_ENDORSED_DIRS;
693 					return true;
694 				} else {
695 					throw new IllegalArgumentException();
696 				}
697 			}
698 			if ("-d".equals(current)) { //$NON-NLS-1$
699 				remaining.remove(); // remove the current option
700 				if (remaining.hasNext()) {
701 					final Iterable<? extends File> outputDir = getOutputDir(remaining.next());
702 					if (outputDir != null) {
703 						setLocation(StandardLocation.CLASS_OUTPUT, outputDir);
704 					}
705 					remaining.remove();
706 					return true;
707 				} else {
708 					throw new IllegalArgumentException();
709 				}
710 			}
711 			if ("-s".equals(current)) { //$NON-NLS-1$
712 				remaining.remove(); // remove the current option
713 				if (remaining.hasNext()) {
714 					final Iterable<? extends File> outputDir = getOutputDir(remaining.next());
715 					if (outputDir != null) {
716 						setLocation(StandardLocation.SOURCE_OUTPUT, outputDir);
717 					}
718 					remaining.remove();
719 					return true;
720 				} else {
721 					throw new IllegalArgumentException();
722 				}
723 			}
724 			if ("-processorpath".equals(current)) {//$NON-NLS-1$
725 				remaining.remove(); // remove the current option
726 				if (remaining.hasNext()) {
727 					final Iterable<? extends File> processorpaths = getPathsFrom(remaining.next());
728 					if (processorpaths != null) {
729 						setLocation(StandardLocation.ANNOTATION_PROCESSOR_PATH, processorpaths);
730 					}
731 					remaining.remove();
732 					this.flags |= HAS_PROCESSORPATH;
733 					return true;
734 				} else {
735 					throw new IllegalArgumentException();
736 				}
737 			}
738 		} catch (IOException e) {
739 			// ignore
740 		}
741 		return false;
742 	}
743 
744 	/* (non-Javadoc)
745 	 * @see javax.tools.JavaFileManager#hasLocation(javax.tools.JavaFileManager.Location)
746 	 */
hasLocation(Location location)747 	public boolean hasLocation(Location location) {
748 		return this.locations != null && this.locations.containsKey(location.getName());
749 	}
750 
751 	/* (non-Javadoc)
752 	 * @see javax.tools.JavaFileManager#inferBinaryName(javax.tools.JavaFileManager.Location, javax.tools.JavaFileObject)
753 	 */
inferBinaryName(Location location, JavaFileObject file)754 	public String inferBinaryName(Location location, JavaFileObject file) {
755 		String name = file.getName();
756 		JavaFileObject javaFileObject = null;
757 		int index = name.lastIndexOf('.');
758 		if (index != -1) {
759 			name = name.substring(0, index);
760 		}
761 		try {
762 			javaFileObject = getJavaFileForInput(location, name, file.getKind());
763 		} catch (IOException e) {
764 			// ignore
765 		} catch (IllegalArgumentException iae) {
766 			return null; // Either unknown kind or location not present
767 		}
768 		if (javaFileObject == null) {
769 			return null;
770 		}
771 		return normalized(name);
772 	}
773 
isArchive(File f)774 	private boolean isArchive(File f) {
775 		String extension = getExtension(f);
776 		return extension.equalsIgnoreCase(".jar") || extension.equalsIgnoreCase(".zip");//$NON-NLS-1$//$NON-NLS-2$
777 	}
778 
779 	/* (non-Javadoc)
780 	 * @see javax.tools.StandardJavaFileManager#isSameFile(javax.tools.FileObject, javax.tools.FileObject)
781 	 */
isSameFile(FileObject fileObject1, FileObject fileObject2)782 	public boolean isSameFile(FileObject fileObject1, FileObject fileObject2) {
783 		// EclipseFileManager creates only EcliseFileObject
784 		if (!(fileObject1 instanceof EclipseFileObject)) throw new IllegalArgumentException("Unsupported file object class : " + fileObject1.getClass());//$NON-NLS-1$
785 		if (!(fileObject2 instanceof EclipseFileObject)) throw new IllegalArgumentException("Unsupported file object class : " + fileObject2.getClass());//$NON-NLS-1$
786 		return fileObject1.equals(fileObject2);
787 	}
788 	/* (non-Javadoc)
789 	 * @see javax.tools.OptionChecker#isSupportedOption(java.lang.String)
790 	 */
isSupportedOption(String option)791 	public int isSupportedOption(String option) {
792 		return Options.processOptionsFileManager(option);
793 	}
794 
795 	/* (non-Javadoc)
796 	 * @see javax.tools.JavaFileManager#list(javax.tools.JavaFileManager.Location, java.lang.String, java.util.Set, boolean)
797 	 */
list(Location location, String packageName, Set<Kind> kinds, boolean recurse)798 	public Iterable<JavaFileObject> list(Location location, String packageName, Set<Kind> kinds, boolean recurse)
799 			throws IOException {
800 
801 		Iterable<? extends File> allFilesInLocations = getLocation(location);
802 		if (allFilesInLocations == null) {
803 			throw new IllegalArgumentException("Unknown location : " + location);//$NON-NLS-1$
804 		}
805 
806 		ArrayList<JavaFileObject> collector = new ArrayList<JavaFileObject>();
807 		String normalizedPackageName = normalized(packageName);
808 		for (File file : allFilesInLocations) {
809 			collectAllMatchingFiles(file, normalizedPackageName, kinds, recurse, collector);
810 		}
811 		return collector;
812 	}
813 
normalized(String className)814 	private String normalized(String className) {
815 		char[] classNameChars = className.toCharArray();
816 		for (int i = 0, max = classNameChars.length; i < max; i++) {
817 			switch(classNameChars[i]) {
818 				case '\\' :
819 					classNameChars[i] = '/';
820 					break;
821 				case '.' :
822 					classNameChars[i] = '/';
823 			}
824 		}
825 		return new String(classNameChars);
826 	}
827 
prependFiles(Iterable<? extends File> iterable, Iterable<? extends File> iterable2)828 	private Iterable<? extends File> prependFiles(Iterable<? extends File> iterable,
829 			Iterable<? extends File> iterable2) {
830 		if (iterable2 == null) return iterable;
831 		ArrayList<File> list = new ArrayList<File>();
832 		for (Iterator<? extends File> iterator = iterable2.iterator(); iterator.hasNext(); ) {
833 			list.add(iterator.next());
834 		}
835 		for (Iterator<? extends File> iterator = iterable.iterator(); iterator.hasNext(); ) {
836 			list.add(iterator.next());
837 		}
838 		return list;
839 	}
840 
841 	/* (non-Javadoc)
842 	 * @see javax.tools.StandardJavaFileManager#setLocation(javax.tools.JavaFileManager.Location, java.lang.Iterable)
843 	 */
setLocation(Location location, Iterable<? extends File> path)844 	public void setLocation(Location location, Iterable<? extends File> path) throws IOException {
845 		if (path != null) {
846 			if (location.isOutputLocation()) {
847 				// output location
848 				int count = 0;
849 				for (Iterator<? extends File> iterator = path.iterator(); iterator.hasNext(); ) {
850 					iterator.next();
851 					count++;
852 				}
853 				if (count != 1) {
854 					throw new IllegalArgumentException("output location can only have one path");//$NON-NLS-1$
855 				}
856 			}
857 			this.locations.put(location.getName(), path);
858 		}
859 	}
860 
setLocale(Locale locale)861 	public void setLocale(Locale locale) {
862 		this.locale = locale == null ? Locale.getDefault() : locale;
863 		try {
864 			this.bundle = ResourceBundleFactory.getBundle(this.locale);
865 		} catch(MissingResourceException e) {
866 			System.out.println("Missing resource : " + Main.bundleName.replace('.', '/') + ".properties for locale " + locale); //$NON-NLS-1$//$NON-NLS-2$
867 			throw e;
868 		}
869 	}
870 
871 	@SuppressWarnings({"rawtypes", "unchecked"})
processPathEntries(final int defaultSize, final ArrayList paths, final String currentPath, String customEncoding, boolean isSourceOnly, boolean rejectDestinationPathOnJars)872 	public void processPathEntries(final int defaultSize, final ArrayList paths,
873 			final String currentPath, String customEncoding, boolean isSourceOnly,
874 			boolean rejectDestinationPathOnJars) {
875 
876 		String currentClasspathName = null;
877 		String currentDestinationPath = null;
878 		ArrayList currentRuleSpecs = new ArrayList(defaultSize);
879 		StringTokenizer tokenizer = new StringTokenizer(currentPath,
880 				File.pathSeparator + "[]", true); //$NON-NLS-1$
881 		ArrayList tokens = new ArrayList();
882 		while (tokenizer.hasMoreTokens()) {
883 			tokens.add(tokenizer.nextToken());
884 		}
885 		// state machine
886 		final int start = 0;
887 		final int readyToClose = 1;
888 		// 'path' 'path1[rule];path2'
889 		final int readyToCloseEndingWithRules = 2;
890 		// 'path[rule]' 'path1;path2[rule]'
891 		final int readyToCloseOrOtherEntry = 3;
892 		// 'path[rule];' 'path;' 'path1;path2;'
893 		final int rulesNeedAnotherRule = 4;
894 		// 'path[rule1;'
895 		final int rulesStart = 5;
896 		// 'path[' 'path1;path2['
897 		final int rulesReadyToClose = 6;
898 		// 'path[rule' 'path[rule1;rule2'
899 		final int destinationPathReadyToClose = 7;
900 		// 'path[-d bin'
901 		final int readyToCloseEndingWithDestinationPath = 8;
902 		// 'path[-d bin]' 'path[rule][-d bin]'
903 		final int destinationPathStart = 9;
904 		// 'path[rule]['
905 		final int bracketOpened = 10;
906 		// '.*[.*'
907 		final int bracketClosed = 11;
908 		// '.*([.*])+'
909 
910 		final int error = 99;
911 		int state = start;
912 		String token = null;
913 		int cursor = 0, tokensNb = tokens.size(), bracket = -1;
914 		while (cursor < tokensNb && state != error) {
915 			token = (String) tokens.get(cursor++);
916 			if (token.equals(File.pathSeparator)) {
917 				switch (state) {
918 				case start:
919 				case readyToCloseOrOtherEntry:
920 				case bracketOpened:
921 					break;
922 				case readyToClose:
923 				case readyToCloseEndingWithRules:
924 				case readyToCloseEndingWithDestinationPath:
925 					state = readyToCloseOrOtherEntry;
926 					addNewEntry(paths, currentClasspathName, currentRuleSpecs,
927 							customEncoding, currentDestinationPath, isSourceOnly,
928 							rejectDestinationPathOnJars);
929 					currentRuleSpecs.clear();
930 					break;
931 				case rulesReadyToClose:
932 					state = rulesNeedAnotherRule;
933 					break;
934 				case destinationPathReadyToClose:
935 					throw new IllegalArgumentException(
936 							this.bind("configure.incorrectDestinationPathEntry", //$NON-NLS-1$
937 									currentPath));
938 				case bracketClosed:
939 					cursor = bracket + 1;
940 					state = rulesStart;
941 					break;
942 				default:
943 					state = error;
944 				}
945 			} else if (token.equals("[")) { //$NON-NLS-1$
946 				switch (state) {
947 				case start:
948 					currentClasspathName = ""; //$NON-NLS-1$
949 					//$FALL-THROUGH$
950 				case readyToClose:
951 					bracket = cursor - 1;
952 					//$FALL-THROUGH$
953 				case bracketClosed:
954 					state = bracketOpened;
955 					break;
956 				case readyToCloseEndingWithRules:
957 					state = destinationPathStart;
958 					break;
959 				case readyToCloseEndingWithDestinationPath:
960 					state = rulesStart;
961 					break;
962 				case bracketOpened:
963 				default:
964 					state = error;
965 				}
966 			} else if (token.equals("]")) { //$NON-NLS-1$
967 				switch (state) {
968 				case rulesReadyToClose:
969 					state = readyToCloseEndingWithRules;
970 					break;
971 				case destinationPathReadyToClose:
972 					state = readyToCloseEndingWithDestinationPath;
973 					break;
974 				case bracketOpened:
975 					state = bracketClosed;
976 					break;
977 				case bracketClosed:
978 				default:
979 					state = error;
980 				}
981 			} else {
982 				// regular word
983 				switch (state) {
984 				case start:
985 				case readyToCloseOrOtherEntry:
986 					state = readyToClose;
987 					currentClasspathName = token;
988 					break;
989 				case rulesStart:
990 					if (token.startsWith("-d ")) { //$NON-NLS-1$
991 						if (currentDestinationPath != null) {
992 							throw new IllegalArgumentException(
993 									this.bind("configure.duplicateDestinationPathEntry", //$NON-NLS-1$
994 											currentPath));
995 						}
996 						currentDestinationPath = token.substring(3).trim();
997 						state = destinationPathReadyToClose;
998 						break;
999 					} // else we proceed with a rule
1000 					//$FALL-THROUGH$
1001 				case rulesNeedAnotherRule:
1002 					if (currentDestinationPath != null) {
1003 						throw new IllegalArgumentException(
1004 								this.bind("configure.accessRuleAfterDestinationPath", //$NON-NLS-1$
1005 									currentPath));
1006 					}
1007 					state = rulesReadyToClose;
1008 					currentRuleSpecs.add(token);
1009 					break;
1010 				case destinationPathStart:
1011 					if (!token.startsWith("-d ")) { //$NON-NLS-1$
1012 						state = error;
1013 					} else {
1014 						currentDestinationPath = token.substring(3).trim();
1015 						state = destinationPathReadyToClose;
1016 					}
1017 					break;
1018 				case bracketClosed:
1019 					for (int i = bracket; i < cursor ; i++) {
1020 						currentClasspathName += (String) tokens.get(i);
1021 					}
1022 					state = readyToClose;
1023 					break;
1024 				case bracketOpened:
1025 					break;
1026 				default:
1027 					state = error;
1028 				}
1029 			}
1030 			if (state == bracketClosed && cursor == tokensNb) {
1031 				cursor = bracket + 1;
1032 				state = rulesStart;
1033 			}
1034 		}
1035 		switch(state) {
1036 			case readyToCloseOrOtherEntry:
1037 				break;
1038 			case readyToClose:
1039 			case readyToCloseEndingWithRules:
1040 			case readyToCloseEndingWithDestinationPath:
1041 				addNewEntry(paths, currentClasspathName, currentRuleSpecs,
1042 					customEncoding, currentDestinationPath, isSourceOnly,
1043 					rejectDestinationPathOnJars);
1044 				break;
1045 			case bracketOpened:
1046 			case bracketClosed:
1047 			default :
1048 				// we go on anyway
1049 		}
1050 	}
1051 	@SuppressWarnings({"rawtypes", "unchecked"})
addNewEntry(ArrayList paths, String currentClasspathName, ArrayList currentRuleSpecs, String customEncoding, String destPath, boolean isSourceOnly, boolean rejectDestinationPathOnJars)1052 	protected void addNewEntry(ArrayList paths, String currentClasspathName,
1053 			ArrayList currentRuleSpecs, String customEncoding,
1054 			String destPath, boolean isSourceOnly,
1055 			boolean rejectDestinationPathOnJars) {
1056 
1057 		int rulesSpecsSize = currentRuleSpecs.size();
1058 		AccessRuleSet accessRuleSet = null;
1059 		if (rulesSpecsSize != 0) {
1060 			AccessRule[] accessRules = new AccessRule[currentRuleSpecs.size()];
1061 			boolean rulesOK = true;
1062 			Iterator i = currentRuleSpecs.iterator();
1063 			int j = 0;
1064 			while (i.hasNext()) {
1065 				String ruleSpec = (String) i.next();
1066 				char key = ruleSpec.charAt(0);
1067 				String pattern = ruleSpec.substring(1);
1068 				if (pattern.length() > 0) {
1069 					switch (key) {
1070 						case '+':
1071 							accessRules[j++] = new AccessRule(pattern
1072 									.toCharArray(), 0);
1073 							break;
1074 						case '~':
1075 							accessRules[j++] = new AccessRule(pattern
1076 									.toCharArray(),
1077 									IProblem.DiscouragedReference);
1078 							break;
1079 						case '-':
1080 							accessRules[j++] = new AccessRule(pattern
1081 									.toCharArray(),
1082 									IProblem.ForbiddenReference);
1083 							break;
1084 						case '?':
1085 							accessRules[j++] = new AccessRule(pattern
1086 									.toCharArray(),
1087 									IProblem.ForbiddenReference, true/*keep looking for accessible type*/);
1088 							break;
1089 						default:
1090 							rulesOK = false;
1091 					}
1092 				} else {
1093 					rulesOK = false;
1094 				}
1095 			}
1096 			if (rulesOK) {
1097 	    		accessRuleSet = new AccessRuleSet(accessRules, AccessRestriction.COMMAND_LINE, currentClasspathName);
1098 			} else {
1099 				return;
1100 			}
1101 		}
1102 		if (Main.NONE.equals(destPath)) {
1103 			destPath = Main.NONE; // keep == comparison valid
1104 		}
1105 		if (rejectDestinationPathOnJars && destPath != null &&
1106 				(currentClasspathName.endsWith(".jar") || //$NON-NLS-1$
1107 					currentClasspathName.endsWith(".zip"))) { //$NON-NLS-1$
1108 			throw new IllegalArgumentException(
1109 					this.bind("configure.unexpectedDestinationPathEntryFile", //$NON-NLS-1$
1110 								currentClasspathName));
1111 			}
1112 		FileSystem.Classpath currentClasspath = FileSystem.getClasspath(
1113 				currentClasspathName,
1114 				customEncoding,
1115 				isSourceOnly,
1116 				accessRuleSet,
1117 				destPath);
1118 		if (currentClasspath != null) {
1119 			paths.add(currentClasspath);
1120 		}
1121 	}
1122 	/*
1123 	 * Lookup the message with the given ID in this catalog and bind its
1124 	 * substitution locations with the given string.
1125 	 */
bind(String id, String binding)1126 	private String bind(String id, String binding) {
1127 		return bind(id, new String[] { binding });
1128 	}
1129 
1130 	/*
1131 	 * Lookup the message with the given ID in this catalog and bind its
1132 	 * substitution locations with the given string values.
1133 	 */
bind(String id, String[] arguments)1134 	private String bind(String id, String[] arguments) {
1135 		if (id == null)
1136 			return "No message available"; //$NON-NLS-1$
1137 		String message = null;
1138 		try {
1139 			message = this.bundle.getString(id);
1140 		} catch (MissingResourceException e) {
1141 			// If we got an exception looking for the message, fail gracefully by just returning
1142 			// the id we were looking for.  In most cases this is semi-informative so is not too bad.
1143 			return "Missing message: " + id + " in: " + Main.bundleName; //$NON-NLS-2$ //$NON-NLS-1$
1144 		}
1145 		return MessageFormat.format(message, (Object[]) arguments);
1146 	}
1147 }
1148