1 /*
2  * Copyright (c) 2009, 2016, Oracle and/or its affiliates. All rights reserved.
3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4  *
5  * This code is free software; you can redistribute it and/or modify it
6  * under the terms of the GNU General Public License version 2 only, as
7  * published by the Free Software Foundation.  Oracle designates this
8  * particular file as subject to the "Classpath" exception as provided
9  * by Oracle in the LICENSE file that accompanied this code.
10  *
11  * This code is distributed in the hope that it will be useful, but WITHOUT
12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14  * version 2 for more details (a copy is included in the LICENSE file that
15  * accompanied this code).
16  *
17  * You should have received a copy of the GNU General Public License version
18  * 2 along with this work; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20  *
21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22  * or visit www.oracle.com if you need additional information or have any
23  * questions.
24  */
25 
26 package com.sun.tools.javac.file;
27 
28 import java.io.IOException;
29 import java.io.InputStream;
30 import java.lang.ref.SoftReference;
31 import java.lang.reflect.Constructor;
32 import java.lang.reflect.InvocationTargetException;
33 import java.lang.reflect.Method;
34 import java.net.URL;
35 import java.net.URLClassLoader;
36 import java.nio.ByteBuffer;
37 import java.nio.CharBuffer;
38 import java.nio.charset.Charset;
39 import java.nio.charset.CharsetDecoder;
40 import java.nio.charset.CoderResult;
41 import java.nio.charset.CodingErrorAction;
42 import java.nio.charset.IllegalCharsetNameException;
43 import java.nio.charset.UnsupportedCharsetException;
44 import java.nio.file.Path;
45 import java.util.Collection;
46 import java.util.HashMap;
47 import java.util.Iterator;
48 import java.util.Map;
49 import java.util.Objects;
50 import java.util.Set;
51 
52 import javax.tools.JavaFileManager;
53 import javax.tools.JavaFileObject;
54 import javax.tools.JavaFileObject.Kind;
55 
56 import com.sun.tools.javac.main.Option;
57 import com.sun.tools.javac.main.OptionHelper;
58 import com.sun.tools.javac.main.OptionHelper.GrumpyHelper;
59 import com.sun.tools.javac.resources.CompilerProperties.Errors;
60 import com.sun.tools.javac.util.Abort;
61 import com.sun.tools.javac.util.Context;
62 import com.sun.tools.javac.util.DefinedBy;
63 import com.sun.tools.javac.util.DefinedBy.Api;
64 import com.sun.tools.javac.util.Log;
65 import com.sun.tools.javac.util.Options;
66 
67 /**
68  * Utility methods for building a filemanager.
69  * There are no references here to file-system specific objects such as
70  * java.io.File or java.nio.file.Path.
71  */
72 public abstract class BaseFileManager implements JavaFileManager {
BaseFileManager(Charset charset)73     protected BaseFileManager(Charset charset) {
74         this.charset = charset;
75         byteBufferCache = new ByteBufferCache();
76         locations = createLocations();
77     }
78 
79     /**
80      * Set the context for JavacPathFileManager.
81      * @param context the context containing items to be associated with the file manager
82      */
setContext(Context context)83     public void setContext(Context context) {
84         log = Log.instance(context);
85         options = Options.instance(context);
86         classLoaderClass = options.get("procloader");
87 
88         // Avoid initializing Lint
89         boolean warn = options.isLintSet("path");
90         locations.update(log, warn, FSInfo.instance(context));
91 
92         // Setting this option is an indication that close() should defer actually closing
93         // the file manager until after a specified period of inactivity.
94         // This is to accomodate clients which save references to Symbols created for use
95         // within doclets or annotation processors, and which then attempt to use those
96         // references after the tool exits, having closed any internally managed file manager.
97         // Ideally, such clients should run the tool via the javax.tools API, providing their
98         // own file manager, which can be closed by the client when all use of that file
99         // manager is complete.
100         // If the option has a numeric value, it will be interpreted as the duration,
101         // in seconds, of the period of inactivity to wait for, before the file manager
102         // is actually closed.
103         // See also deferredClose().
104         String s = options.get("fileManager.deferClose");
105         if (s != null) {
106             try {
107                 deferredCloseTimeout = (int) (Float.parseFloat(s) * 1000);
108             } catch (NumberFormatException e) {
109                 deferredCloseTimeout = 60 * 1000;  // default: one minute, in millis
110             }
111         }
112     }
113 
createLocations()114     protected Locations createLocations() {
115         return new Locations();
116     }
117 
118     /**
119      * The log to be used for error reporting.
120      */
121     public Log log;
122 
123     /**
124      * User provided charset (through javax.tools).
125      */
126     protected Charset charset;
127 
128     protected Options options;
129 
130     protected String classLoaderClass;
131 
132     protected final Locations locations;
133 
134     /**
135      * A flag for clients to use to indicate that this file manager should
136      * be closed when it is no longer required.
137      */
138     public boolean autoClose;
139 
140     /**
141      * Wait for a period of inactivity before calling close().
142      * The length of the period of inactivity is given by {@code deferredCloseTimeout}
143      */
deferredClose()144     protected void deferredClose() {
145         Thread t = new Thread(getClass().getName() + " DeferredClose") {
146             @Override
147             public void run() {
148                 try {
149                     synchronized (BaseFileManager.this) {
150                         long now = System.currentTimeMillis();
151                         while (now < lastUsedTime + deferredCloseTimeout) {
152                             BaseFileManager.this.wait(lastUsedTime + deferredCloseTimeout - now);
153                             now = System.currentTimeMillis();
154                         }
155                         deferredCloseTimeout = 0;
156                         close();
157                     }
158                 } catch (InterruptedException e) {
159                 } catch (IOException e) {
160                 }
161             }
162         };
163         t.setDaemon(true);
164         t.start();
165     }
166 
updateLastUsedTime()167     synchronized void updateLastUsedTime() {
168         if (deferredCloseTimeout > 0) { // avoid updating the time unnecessarily
169             lastUsedTime = System.currentTimeMillis();
170         }
171     }
172 
173     private long lastUsedTime = System.currentTimeMillis();
174     protected long deferredCloseTimeout = 0;
175 
getClassLoader(URL[] urls)176     protected ClassLoader getClassLoader(URL[] urls) {
177         ClassLoader thisClassLoader = getClass().getClassLoader();
178 
179         // Allow the following to specify a closeable classloader
180         // other than URLClassLoader.
181 
182         // 1: Allow client to specify the class to use via hidden option
183         if (classLoaderClass != null) {
184             try {
185                 Class<? extends ClassLoader> loader =
186                         Class.forName(classLoaderClass).asSubclass(ClassLoader.class);
187                 Class<?>[] constrArgTypes = { URL[].class, ClassLoader.class };
188                 Constructor<? extends ClassLoader> constr = loader.getConstructor(constrArgTypes);
189                 return constr.newInstance(urls, thisClassLoader);
190             } catch (ReflectiveOperationException t) {
191                 // ignore errors loading user-provided class loader, fall through
192             }
193         }
194         return new URLClassLoader(urls, thisClassLoader);
195     }
196 
isDefaultBootClassPath()197     public boolean isDefaultBootClassPath() {
198         return locations.isDefaultBootClassPath();
199     }
200 
201     // <editor-fold defaultstate="collapsed" desc="Option handling">
202     @Override @DefinedBy(Api.COMPILER)
handleOption(String current, Iterator<String> remaining)203     public boolean handleOption(String current, Iterator<String> remaining) {
204         OptionHelper helper = new GrumpyHelper(log) {
205             @Override
206             public String get(Option option) {
207                 return options.get(option);
208             }
209 
210             @Override
211             public void put(String name, String value) {
212                 options.put(name, value);
213             }
214 
215             @Override
216             public void remove(String name) {
217                 options.remove(name);
218             }
219 
220             @Override
221             public boolean handleFileManagerOption(Option option, String value) {
222                 return handleOption(option, value);
223             }
224         };
225 
226         Option o = Option.lookup(current, javacFileManagerOptions);
227         if (o == null) {
228             return false;
229         }
230 
231         try {
232             o.handleOption(helper, current, remaining);
233         } catch (Option.InvalidValueException e) {
234             throw new IllegalArgumentException(e.getMessage(), e);
235         }
236 
237         return true;
238     }
239     // where
240         protected static final Set<Option> javacFileManagerOptions =
241             Option.getJavacFileManagerOptions();
242 
243     @Override @DefinedBy(Api.COMPILER)
isSupportedOption(String option)244     public int isSupportedOption(String option) {
245         Option o = Option.lookup(option, javacFileManagerOptions);
246         return (o == null) ? -1 : o.hasArg() ? 1 : 0;
247     }
248 
249     protected String multiReleaseValue;
250 
251     /**
252      * Common back end for OptionHelper handleFileManagerOption.
253      * @param option the option whose value to be set
254      * @param value the value for the option
255      * @return true if successful, and false otherwise
256      */
handleOption(Option option, String value)257     public boolean handleOption(Option option, String value) {
258         switch (option) {
259             case ENCODING:
260                 encodingName = value;
261                 return true;
262 
263             case MULTIRELEASE:
264                 multiReleaseValue = value;
265                 locations.setMultiReleaseValue(value);
266                 return true;
267 
268             default:
269                 return locations.handleOption(option, value);
270         }
271     }
272 
273     /**
274      * Call handleOption for collection of options and corresponding values.
275      * @param map a collection of options and corresponding values
276      * @return true if all the calls are successful
277      */
handleOptions(Map<Option, String> map)278     public boolean handleOptions(Map<Option, String> map) {
279         boolean ok = true;
280         for (Map.Entry<Option, String> e: map.entrySet()) {
281             try {
282                 ok = ok & handleOption(e.getKey(), e.getValue());
283             } catch (IllegalArgumentException ex) {
284                 log.error(Errors.IllegalArgumentForOption(e.getKey().getPrimaryName(), ex.getMessage()));
285                 ok = false;
286             }
287         }
288         return ok;
289     }
290 
291     // </editor-fold>
292 
293     // <editor-fold defaultstate="collapsed" desc="Encoding">
294     private String encodingName;
295     private String defaultEncodingName;
getDefaultEncodingName()296     private String getDefaultEncodingName() {
297         if (defaultEncodingName == null) {
298             defaultEncodingName = Charset.defaultCharset().name();
299         }
300         return defaultEncodingName;
301     }
302 
getEncodingName()303     public String getEncodingName() {
304         return (encodingName != null) ? encodingName : getDefaultEncodingName();
305     }
306 
decode(ByteBuffer inbuf, boolean ignoreEncodingErrors)307     public CharBuffer decode(ByteBuffer inbuf, boolean ignoreEncodingErrors) {
308         String encName = getEncodingName();
309         CharsetDecoder decoder;
310         try {
311             decoder = getDecoder(encName, ignoreEncodingErrors);
312         } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
313             log.error(Errors.UnsupportedEncoding(encName));
314             return CharBuffer.allocate(1).flip();
315         }
316 
317         // slightly overestimate the buffer size to avoid reallocation.
318         float factor =
319             decoder.averageCharsPerByte() * 0.8f +
320             decoder.maxCharsPerByte() * 0.2f;
321         CharBuffer dest = CharBuffer.
322             allocate(10 + (int)(inbuf.remaining()*factor));
323 
324         while (true) {
325             CoderResult result = decoder.decode(inbuf, dest, true);
326             dest.flip();
327 
328             if (result.isUnderflow()) { // done reading
329                 // make sure there is at least one extra character
330                 if (dest.limit() == dest.capacity()) {
331                     dest = CharBuffer.allocate(dest.capacity()+1).put(dest);
332                     dest.flip();
333                 }
334                 return dest;
335             } else if (result.isOverflow()) { // buffer too small; expand
336                 int newCapacity =
337                     10 + dest.capacity() +
338                     (int)(inbuf.remaining()*decoder.maxCharsPerByte());
339                 dest = CharBuffer.allocate(newCapacity).put(dest);
340             } else if (result.isMalformed() || result.isUnmappable()) {
341                 // bad character in input
342                 StringBuilder unmappable = new StringBuilder();
343                 int len = result.length();
344 
345                 for (int i = 0; i < len; i++) {
346                     unmappable.append(String.format("%02X", inbuf.get()));
347                 }
348 
349                 String charsetName = charset == null ? encName : charset.name();
350 
351                 log.error(dest.limit(),
352                           Errors.IllegalCharForEncoding(unmappable.toString(), charsetName));
353 
354                 // undo the flip() to prepare the output buffer
355                 // for more translation
356                 dest.position(dest.limit());
357                 dest.limit(dest.capacity());
358                 dest.put((char)0xfffd); // backward compatible
359             } else {
360                 throw new AssertionError(result);
361             }
362         }
363         // unreached
364     }
365 
getDecoder(String encodingName, boolean ignoreEncodingErrors)366     public CharsetDecoder getDecoder(String encodingName, boolean ignoreEncodingErrors) {
367         Charset cs = (this.charset == null)
368             ? Charset.forName(encodingName)
369             : this.charset;
370         CharsetDecoder decoder = cs.newDecoder();
371 
372         CodingErrorAction action;
373         if (ignoreEncodingErrors)
374             action = CodingErrorAction.REPLACE;
375         else
376             action = CodingErrorAction.REPORT;
377 
378         return decoder
379             .onMalformedInput(action)
380             .onUnmappableCharacter(action);
381     }
382     // </editor-fold>
383 
384     // <editor-fold defaultstate="collapsed" desc="ByteBuffers">
385     /**
386      * Make a byte buffer from an input stream.
387      * @param in the stream
388      * @return a byte buffer containing the contents of the stream
389      * @throws IOException if an error occurred while reading the stream
390      */
makeByteBuffer(InputStream in)391     public ByteBuffer makeByteBuffer(InputStream in)
392         throws IOException {
393         int limit = in.available();
394         if (limit < 1024) limit = 1024;
395         ByteBuffer result = byteBufferCache.get(limit);
396         int position = 0;
397         while (in.available() != 0) {
398             if (position >= limit)
399                 // expand buffer
400                 result = ByteBuffer.
401                     allocate(limit <<= 1).
402                     put(result.flip());
403             int count = in.read(result.array(),
404                 position,
405                 limit - position);
406             if (count < 0) break;
407             result.position(position += count);
408         }
409         return result.flip();
410     }
411 
recycleByteBuffer(ByteBuffer bb)412     public void recycleByteBuffer(ByteBuffer bb) {
413         byteBufferCache.put(bb);
414     }
415 
416     /**
417      * A single-element cache of direct byte buffers.
418      */
419     private static class ByteBufferCache {
420         private ByteBuffer cached;
get(int capacity)421         ByteBuffer get(int capacity) {
422             if (capacity < 20480) capacity = 20480;
423             ByteBuffer result =
424                 (cached != null && cached.capacity() >= capacity)
425                 ? cached.clear()
426                 : ByteBuffer.allocate(capacity + capacity>>1);
427             cached = null;
428             return result;
429         }
put(ByteBuffer x)430         void put(ByteBuffer x) {
431             cached = x;
432         }
433     }
434 
435     private final ByteBufferCache byteBufferCache;
436     // </editor-fold>
437 
438     // <editor-fold defaultstate="collapsed" desc="Content cache">
getCachedContent(JavaFileObject file)439     public CharBuffer getCachedContent(JavaFileObject file) {
440         ContentCacheEntry e = contentCache.get(file);
441         if (e == null)
442             return null;
443 
444         if (!e.isValid(file)) {
445             contentCache.remove(file);
446             return null;
447         }
448 
449         return e.getValue();
450     }
451 
cache(JavaFileObject file, CharBuffer cb)452     public void cache(JavaFileObject file, CharBuffer cb) {
453         contentCache.put(file, new ContentCacheEntry(file, cb));
454     }
455 
flushCache(JavaFileObject file)456     public void flushCache(JavaFileObject file) {
457         contentCache.remove(file);
458     }
459 
460     protected final Map<JavaFileObject, ContentCacheEntry> contentCache = new HashMap<>();
461 
462     protected static class ContentCacheEntry {
463         final long timestamp;
464         final SoftReference<CharBuffer> ref;
465 
ContentCacheEntry(JavaFileObject file, CharBuffer cb)466         ContentCacheEntry(JavaFileObject file, CharBuffer cb) {
467             this.timestamp = file.getLastModified();
468             this.ref = new SoftReference<>(cb);
469         }
470 
isValid(JavaFileObject file)471         boolean isValid(JavaFileObject file) {
472             return timestamp == file.getLastModified();
473         }
474 
getValue()475         CharBuffer getValue() {
476             return ref.get();
477         }
478     }
479     // </editor-fold>
480 
getKind(Path path)481     public static Kind getKind(Path path) {
482         return getKind(path.getFileName().toString());
483     }
484 
getKind(String name)485     public static Kind getKind(String name) {
486         if (name.endsWith(Kind.CLASS.extension))
487             return Kind.CLASS;
488         else if (name.endsWith(Kind.SOURCE.extension))
489             return Kind.SOURCE;
490         else if (name.endsWith(Kind.HTML.extension))
491             return Kind.HTML;
492         else
493             return Kind.OTHER;
494     }
495 
nullCheck(T o)496     protected static <T> T nullCheck(T o) {
497         return Objects.requireNonNull(o);
498     }
499 
nullCheck(Collection<T> it)500     protected static <T> Collection<T> nullCheck(Collection<T> it) {
501         for (T t : it)
502             Objects.requireNonNull(t);
503         return it;
504     }
505 }
506