PathFileObject.java revision 2983:599930f29330
1172106Srwatson/*
2172106Srwatson * Copyright (c) 2009, 2015, Oracle and/or its affiliates. All rights reserved.
3172106Srwatson * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4172106Srwatson *
5172106Srwatson * This code is free software; you can redistribute it and/or modify it
6172106Srwatson * under the terms of the GNU General Public License version 2 only, as
7172106Srwatson * published by the Free Software Foundation.  Oracle designates this
8172106Srwatson * particular file as subject to the "Classpath" exception as provided
9172106Srwatson * by Oracle in the LICENSE file that accompanied this code.
10172106Srwatson *
11172106Srwatson * This code is distributed in the hope that it will be useful, but WITHOUT
12172106Srwatson * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13172106Srwatson * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14172106Srwatson * version 2 for more details (a copy is included in the LICENSE file that
15172106Srwatson * accompanied this code).
16172106Srwatson *
17172106Srwatson * You should have received a copy of the GNU General Public License version
18172106Srwatson * 2 along with this work; if not, write to the Free Software Foundation,
19172106Srwatson * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20172106Srwatson *
21172106Srwatson * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22172106Srwatson * or visit www.oracle.com if you need additional information or have any
23172106Srwatson * questions.
24172106Srwatson */
25172106Srwatson
26172106Srwatsonpackage com.sun.tools.javac.file;
27172106Srwatson
28172106Srwatsonimport java.io.IOException;
29172106Srwatsonimport java.io.InputStream;
30172106Srwatsonimport java.io.InputStreamReader;
31172106Srwatsonimport java.io.OutputStream;
32172106Srwatsonimport java.io.OutputStreamWriter;
33172106Srwatsonimport java.io.Reader;
34228975Suqsimport java.io.Writer;
35172106Srwatsonimport java.net.URI;
36172106Srwatsonimport java.nio.ByteBuffer;
37172106Srwatsonimport java.nio.CharBuffer;
38172106Srwatsonimport java.nio.charset.CharsetDecoder;
39172106Srwatsonimport java.nio.file.Files;
40172106Srwatsonimport java.nio.file.LinkOption;
41172106Srwatsonimport java.nio.file.Path;
42172106Srwatsonimport java.util.Objects;
43172106Srwatson
44172106Srwatsonimport javax.lang.model.element.Modifier;
45172106Srwatsonimport javax.lang.model.element.NestingKind;
46172106Srwatsonimport javax.tools.JavaFileObject;
47172106Srwatson
48172106Srwatsonimport com.sun.tools.javac.util.DefinedBy;
49172106Srwatsonimport com.sun.tools.javac.util.DefinedBy.Api;
50172106Srwatson
51172106Srwatson
52172106Srwatson/**
53172106Srwatson *  Implementation of JavaFileObject using java.nio.file API.
54172106Srwatson *
55172106Srwatson *  <p>PathFileObjects are, for the most part, straightforward wrappers around
56172106Srwatson *  Path objects. The primary complexity is the support for "inferBinaryName".
57172106Srwatson *  This is left as an abstract method, implemented by each of a number of
58172106Srwatson *  different factory methods, which compute the binary name based on
59172106Srwatson *  information available at the time the file object is created.
60172106Srwatson *
61172106Srwatson *  <p><b>This is NOT part of any supported API.
62172106Srwatson *  If you write code that depends on this, you do so at your own risk.
63172106Srwatson *  This code and its internal interfaces are subject to change or
64172106Srwatson *  deletion without notice.</b>
65172106Srwatson */
66172106Srwatsonpublic abstract class PathFileObject implements JavaFileObject {
67172106Srwatson    private final BaseFileManager fileManager;
68172106Srwatson    private final Path path;
69172106Srwatson
70172106Srwatson    /**
71172106Srwatson     * Create a PathFileObject within a directory, such that the binary name
72172106Srwatson     * can be inferred from the relationship to the parent directory.
73172106Srwatson     */
74172106Srwatson    static PathFileObject createDirectoryPathFileObject(BaseFileManager fileManager,
75172106Srwatson            final Path path, final Path dir) {
76172106Srwatson        return new PathFileObject(fileManager, path) {
77172106Srwatson            @Override
78172106Srwatson            public String inferBinaryName(Iterable<? extends Path> paths) {
79172106Srwatson                return toBinaryName(dir.relativize(path));
80172106Srwatson            }
81172106Srwatson        };
82172106Srwatson    }
83172106Srwatson
84172106Srwatson    /**
85172106Srwatson     * Create a PathFileObject in a file system such as a jar file, such that
86172106Srwatson     * the binary name can be inferred from its position within the filesystem.
87172106Srwatson     */
88172106Srwatson    public static PathFileObject createJarPathFileObject(BaseFileManager fileManager,
89172106Srwatson            final Path path) {
90172106Srwatson        return new PathFileObject(fileManager, path) {
91172106Srwatson            @Override
92172106Srwatson            public String inferBinaryName(Iterable<? extends Path> paths) {
93172106Srwatson                return toBinaryName(path);
94172106Srwatson            }
95172106Srwatson        };
96172106Srwatson    }
97172106Srwatson
98172106Srwatson    /**
99172106Srwatson     * Create a PathFileObject in a modular file system, such as jrt:, such that
100172106Srwatson     * the binary name can be inferred from its position within the filesystem.
101172106Srwatson     */
102172106Srwatson    public static PathFileObject createJRTPathFileObject(BaseFileManager fileManager,
103172106Srwatson            final Path path) {
104172106Srwatson        return new PathFileObject(fileManager, path) {
105172106Srwatson            @Override
106172106Srwatson            public String inferBinaryName(Iterable<? extends Path> paths) {
107172106Srwatson                // use subpath to ignore the leading /modules/MODULE-NAME
108172106Srwatson                return toBinaryName(path.subpath(2, path.getNameCount()));
109172106Srwatson            }
110172106Srwatson        };
111172106Srwatson    }
112172106Srwatson
113172106Srwatson    /**
114172106Srwatson     * Create a PathFileObject whose binary name can be inferred from the
115172106Srwatson     * relative path to a sibling.
116172106Srwatson     */
117172106Srwatson    static PathFileObject createSiblingPathFileObject(BaseFileManager fileManager,
118172106Srwatson            final Path path, final String relativePath) {
119172106Srwatson        return new PathFileObject(fileManager, path) {
120172106Srwatson            @Override
121172106Srwatson            public String inferBinaryName(Iterable<? extends Path> paths) {
122172106Srwatson                return toBinaryName(relativePath, "/");
123172106Srwatson            }
124172106Srwatson        };
125172106Srwatson    }
126172106Srwatson
127172106Srwatson    /**
128172106Srwatson     * Create a PathFileObject whose binary name might be inferred from its
129172106Srwatson     * position on a search path.
130172106Srwatson     */
131172106Srwatson    static PathFileObject createSimplePathFileObject(BaseFileManager fileManager,
132172106Srwatson            final Path path) {
133172106Srwatson        return new PathFileObject(fileManager, path) {
134172106Srwatson            @Override
135172106Srwatson            public String inferBinaryName(Iterable<? extends Path> paths) {
136172106Srwatson                Path absPath = path.toAbsolutePath();
137172106Srwatson                for (Path p: paths) {
138172106Srwatson                    Path ap = p.toAbsolutePath();
139172106Srwatson                    if (absPath.startsWith(ap)) {
140172106Srwatson                        try {
141172106Srwatson                            Path rp = ap.relativize(absPath);
142172106Srwatson                            if (rp != null) // maybe null if absPath same as ap
143172106Srwatson                                return toBinaryName(rp);
144172106Srwatson                        } catch (IllegalArgumentException e) {
145172106Srwatson                            // ignore this p if cannot relativize path to p
146172106Srwatson                        }
147172106Srwatson                    }
148172106Srwatson                }
149172106Srwatson                return null;
150172106Srwatson            }
151172106Srwatson        };
152172106Srwatson    }
153172106Srwatson
154172106Srwatson    protected PathFileObject(BaseFileManager fileManager, Path path) {
155172106Srwatson        this.fileManager = Objects.requireNonNull(fileManager);
156172106Srwatson        this.path = Objects.requireNonNull(path);
157172106Srwatson    }
158172106Srwatson
159172106Srwatson    public abstract String inferBinaryName(Iterable<? extends Path> paths);
160172106Srwatson
161172106Srwatson    /**
162172106Srwatson     * Return the Path for this object.
163172106Srwatson     * @return the Path for this object.
164172106Srwatson     */
165172106Srwatson    public Path getPath() {
166172106Srwatson        return path;
167172106Srwatson    }
168172106Srwatson
169172106Srwatson    @Override @DefinedBy(Api.COMPILER)
170172106Srwatson    public Kind getKind() {
171172106Srwatson        return BaseFileManager.getKind(path.getFileName().toString());
172172106Srwatson    }
173172106Srwatson
174172106Srwatson    @Override @DefinedBy(Api.COMPILER)
175172106Srwatson    public boolean isNameCompatible(String simpleName, Kind kind) {
176172106Srwatson        Objects.requireNonNull(simpleName);
177172106Srwatson        // null check
178172106Srwatson        if (kind == Kind.OTHER && getKind() != kind) {
179172106Srwatson            return false;
180172106Srwatson        }
181172106Srwatson        String sn = simpleName + kind.extension;
182172106Srwatson        String pn = path.getFileName().toString();
183172106Srwatson        if (pn.equals(sn)) {
184172106Srwatson            return true;
185172106Srwatson        }
186172106Srwatson        if (pn.equalsIgnoreCase(sn)) {
187172106Srwatson            try {
188172106Srwatson                // allow for Windows
189172106Srwatson                return path.toRealPath(LinkOption.NOFOLLOW_LINKS).getFileName().toString().equals(sn);
190172106Srwatson            } catch (IOException e) {
191172106Srwatson            }
192172106Srwatson        }
193172106Srwatson        return false;
194172106Srwatson    }
195172106Srwatson
196172106Srwatson    @Override @DefinedBy(Api.COMPILER)
197172106Srwatson    public NestingKind getNestingKind() {
198172106Srwatson        return null;
199172106Srwatson    }
200172106Srwatson
201172106Srwatson    @Override @DefinedBy(Api.COMPILER)
202172106Srwatson    public Modifier getAccessLevel() {
203172106Srwatson        return null;
204172106Srwatson    }
205172106Srwatson
206172106Srwatson    @Override @DefinedBy(Api.COMPILER)
207172106Srwatson    public URI toUri() {
208172106Srwatson        return path.toUri();
209    }
210
211    @Override @DefinedBy(Api.COMPILER)
212    public String getName() {
213        return path.toString();
214    }
215
216    @Override @DefinedBy(Api.COMPILER)
217    public InputStream openInputStream() throws IOException {
218        return Files.newInputStream(path);
219    }
220
221    @Override @DefinedBy(Api.COMPILER)
222    public OutputStream openOutputStream() throws IOException {
223        fileManager.flushCache(this);
224        ensureParentDirectoriesExist();
225        return Files.newOutputStream(path);
226    }
227
228    @Override @DefinedBy(Api.COMPILER)
229    public Reader openReader(boolean ignoreEncodingErrors) throws IOException {
230        CharsetDecoder decoder = fileManager.getDecoder(fileManager.getEncodingName(), ignoreEncodingErrors);
231        return new InputStreamReader(openInputStream(), decoder);
232    }
233
234    @Override @DefinedBy(Api.COMPILER)
235    public CharSequence getCharContent(boolean ignoreEncodingErrors) throws IOException {
236        CharBuffer cb = fileManager.getCachedContent(this);
237        if (cb == null) {
238            try (InputStream in = openInputStream()) {
239                ByteBuffer bb = fileManager.makeByteBuffer(in);
240                JavaFileObject prev = fileManager.log.useSource(this);
241                try {
242                    cb = fileManager.decode(bb, ignoreEncodingErrors);
243                } finally {
244                    fileManager.log.useSource(prev);
245                }
246                fileManager.recycleByteBuffer(bb);
247                if (!ignoreEncodingErrors) {
248                    fileManager.cache(this, cb);
249                }
250            }
251        }
252        return cb;
253    }
254
255    @Override @DefinedBy(Api.COMPILER)
256    public Writer openWriter() throws IOException {
257        fileManager.flushCache(this);
258        ensureParentDirectoriesExist();
259        return new OutputStreamWriter(Files.newOutputStream(path), fileManager.getEncodingName());
260    }
261
262    @Override @DefinedBy(Api.COMPILER)
263    public long getLastModified() {
264        try {
265            return Files.getLastModifiedTime(path).toMillis();
266        } catch (IOException e) {
267            return -1;
268        }
269    }
270
271    @Override @DefinedBy(Api.COMPILER)
272    public boolean delete() {
273        try {
274            Files.delete(path);
275            return true;
276        } catch (IOException e) {
277            return false;
278        }
279    }
280
281    public boolean isSameFile(PathFileObject other) {
282        try {
283            return Files.isSameFile(path, other.path);
284        } catch (IOException e) {
285            return false;
286        }
287    }
288
289    @Override
290    public boolean equals(Object other) {
291        return (other instanceof PathFileObject && path.equals(((PathFileObject) other).path));
292    }
293
294    @Override
295    public int hashCode() {
296        return path.hashCode();
297    }
298
299    @Override
300    public String toString() {
301        return getClass().getSimpleName() + "[" + path + "]";
302    }
303
304    private void ensureParentDirectoriesExist() throws IOException {
305        Path parent = path.getParent();
306        if (parent != null)
307            Files.createDirectories(parent);
308    }
309
310    private long size() {
311        try {
312            return Files.size(path);
313        } catch (IOException e) {
314            return -1;
315        }
316    }
317
318    protected static String toBinaryName(Path relativePath) {
319        return toBinaryName(relativePath.toString(),
320                relativePath.getFileSystem().getSeparator());
321    }
322
323    protected static String toBinaryName(String relativePath, String sep) {
324        return removeExtension(relativePath).replace(sep, ".");
325    }
326
327    protected static String removeExtension(String fileName) {
328        int lastDot = fileName.lastIndexOf(".");
329        return (lastDot == -1 ? fileName : fileName.substring(0, lastDot));
330    }
331}
332