ModuleFinder.java revision 4059:43a83431f19d
1/*
2 * Copyright (c) 2015, 2017, 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 */
25package com.sun.tools.javac.code;
26
27import java.io.IOException;
28import java.util.Arrays;
29import java.util.HashMap;
30import java.util.Iterator;
31import java.util.Map;
32import java.util.NoSuchElementException;
33import java.util.Set;
34import java.util.function.Function;
35
36import javax.tools.JavaFileManager;
37import javax.tools.JavaFileManager.Location;
38import javax.tools.JavaFileObject;
39import javax.tools.JavaFileObject.Kind;
40import javax.tools.StandardLocation;
41
42import com.sun.tools.javac.code.Symbol.ClassSymbol;
43import com.sun.tools.javac.code.Symbol.Completer;
44import com.sun.tools.javac.code.Symbol.CompletionFailure;
45import com.sun.tools.javac.code.Symbol.ModuleSymbol;
46import com.sun.tools.javac.jvm.ModuleNameReader;
47import com.sun.tools.javac.jvm.ModuleNameReader.BadClassFile;
48import com.sun.tools.javac.resources.CompilerProperties.Errors;
49import com.sun.tools.javac.resources.CompilerProperties.Fragments;
50import com.sun.tools.javac.util.Assert;
51import com.sun.tools.javac.util.Context;
52import com.sun.tools.javac.util.JCDiagnostic;
53import com.sun.tools.javac.util.JCDiagnostic.Fragment;
54import com.sun.tools.javac.util.List;
55import com.sun.tools.javac.util.ListBuffer;
56import com.sun.tools.javac.util.Log;
57import com.sun.tools.javac.util.Name;
58import com.sun.tools.javac.util.Names;
59
60import static com.sun.tools.javac.code.Kinds.Kind.*;
61
62/**
63 *  This class provides operations to locate module definitions
64 *  from the source and class files on the paths provided to javac.
65 *
66 *  <p><b>This is NOT part of any supported API.
67 *  If you write code that depends on this, you do so at your own risk.
68 *  This code and its internal interfaces are subject to change or
69 *  deletion without notice.</b>
70 */
71public class ModuleFinder {
72    /** The context key for the module finder. */
73    protected static final Context.Key<ModuleFinder> moduleFinderKey = new Context.Key<>();
74
75    /** The log to use for verbose output. */
76    private final Log log;
77
78    /** The symbol table. */
79    private final Symtab syms;
80
81    /** The name table. */
82    private final Names names;
83
84    private final ClassFinder classFinder;
85
86    /** Access to files
87     */
88    private final JavaFileManager fileManager;
89
90    private final JCDiagnostic.Factory diags;
91
92    private ModuleNameReader moduleNameReader;
93
94    public ModuleNameFromSourceReader moduleNameFromSourceReader;
95
96    /** Get the ModuleFinder instance for this invocation. */
97    public static ModuleFinder instance(Context context) {
98        ModuleFinder instance = context.get(moduleFinderKey);
99        if (instance == null)
100            instance = new ModuleFinder(context);
101        return instance;
102    }
103
104    /** Construct a new module finder. */
105    protected ModuleFinder(Context context) {
106        context.put(moduleFinderKey, this);
107        names = Names.instance(context);
108        syms = Symtab.instance(context);
109        fileManager = context.get(JavaFileManager.class);
110        log = Log.instance(context);
111        classFinder = ClassFinder.instance(context);
112
113        diags = JCDiagnostic.Factory.instance(context);
114    }
115
116    class ModuleLocationIterator implements Iterator<Set<Location>> {
117        StandardLocation outer;
118        Set<Location> next = null;
119
120        Iterator<StandardLocation> outerIter = Arrays.asList(
121                StandardLocation.MODULE_SOURCE_PATH,
122                StandardLocation.UPGRADE_MODULE_PATH,
123                StandardLocation.SYSTEM_MODULES,
124                StandardLocation.MODULE_PATH
125        ).iterator();
126        Iterator<Set<Location>> innerIter = null;
127
128        @Override
129        public boolean hasNext() {
130            while (next == null) {
131                while (innerIter == null || !innerIter.hasNext()) {
132                    if (outerIter.hasNext()) {
133                        outer = outerIter.next();
134                        try {
135                            innerIter = fileManager.listLocationsForModules(outer).iterator();
136                        } catch (IOException e) {
137                            System.err.println("error listing module locations for " + outer + ": " + e);  // FIXME
138                        }
139                    } else
140                        return false;
141                }
142
143                if (innerIter.hasNext())
144                    next = innerIter.next();
145            }
146            return true;
147        }
148
149        @Override
150        public Set<Location> next() {
151            hasNext();
152            if (next != null) {
153                Set<Location> result = next;
154                next = null;
155                return result;
156            }
157            throw new NoSuchElementException();
158        }
159
160    }
161
162    ModuleLocationIterator moduleLocationIterator = new ModuleLocationIterator();
163
164    public ModuleSymbol findModule(Name name) {
165        return findModule(syms.enterModule(name));
166    }
167
168    public ModuleSymbol findModule(ModuleSymbol msym) {
169        if (msym.kind != ERR && msym.sourceLocation == null && msym.classLocation == null) {
170            // fill in location
171            List<ModuleSymbol> list = scanModulePath(msym);
172            if (list.isEmpty()) {
173                msym.kind = ERR;
174            }
175        }
176        if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
177            // fill in module-info
178            findModuleInfo(msym);
179        }
180        return msym;
181    }
182
183    public List<ModuleSymbol> findAllModules() {
184        List<ModuleSymbol> list = scanModulePath(null);
185        for (ModuleSymbol msym: list) {
186            if (msym.kind != ERR && msym.module_info.sourcefile == null && msym.module_info.classfile == null) {
187                // fill in module-info
188                findModuleInfo(msym);
189            }
190        }
191        return list;
192    }
193
194    public ModuleSymbol findSingleModule() {
195        try {
196            JavaFileObject src_fo = getModuleInfoFromLocation(StandardLocation.SOURCE_PATH, Kind.SOURCE);
197            JavaFileObject class_fo = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
198            JavaFileObject fo = (src_fo == null) ? class_fo
199                    : (class_fo == null) ? src_fo
200                            : classFinder.preferredFileObject(src_fo, class_fo);
201
202            ModuleSymbol msym;
203            if (fo == null) {
204                msym = syms.unnamedModule;
205            } else {
206                msym = readModule(fo);
207            }
208
209            if (msym.patchLocation == null) {
210                msym.classLocation = StandardLocation.CLASS_OUTPUT;
211            } else {
212                msym.patchOutputLocation = StandardLocation.CLASS_OUTPUT;
213            }
214            return msym;
215
216        } catch (IOException e) {
217            throw new Error(e); // FIXME
218        }
219    }
220
221    private ModuleSymbol readModule(JavaFileObject fo) throws IOException {
222        Name name;
223        switch (fo.getKind()) {
224            case SOURCE:
225                name = moduleNameFromSourceReader.readModuleName(fo);
226                if (name == null) {
227                    JCDiagnostic diag =
228                        diags.fragment("file.does.not.contain.module");
229                    ClassSymbol errModuleInfo = syms.defineClass(names.module_info, syms.errModule);
230                    throw new ClassFinder.BadClassFile(errModuleInfo, fo, diag, diags);
231                }
232                break;
233            case CLASS:
234                try {
235                    name = names.fromString(readModuleName(fo));
236                } catch (BadClassFile | IOException ex) {
237                    //fillIn will report proper errors:
238                    name = names.error;
239                }
240                break;
241            default:
242                Assert.error();
243                name = names.error;
244                break;
245        }
246
247        ModuleSymbol msym = syms.enterModule(name);
248        msym.module_info.classfile = fo;
249        if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) && name != names.error) {
250            msym.patchLocation = fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH, name.toString());
251
252            if (msym.patchLocation != null) {
253                JavaFileObject patchFO = getModuleInfoFromLocation(StandardLocation.CLASS_OUTPUT, Kind.CLASS);
254                patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), patchFO);
255                patchFO = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), patchFO);
256
257                if (patchFO != null) {
258                    msym.module_info.classfile = patchFO;
259                }
260            }
261        }
262
263        msym.completer = Completer.NULL_COMPLETER;
264        classFinder.fillIn(msym.module_info);
265
266        return msym;
267    }
268
269    private String readModuleName(JavaFileObject jfo) throws IOException, ModuleNameReader.BadClassFile {
270        if (moduleNameReader == null)
271            moduleNameReader = new ModuleNameReader();
272        return moduleNameReader.readModuleName(jfo);
273    }
274
275    private JavaFileObject getModuleInfoFromLocation(Location location, Kind kind) throws IOException {
276        if (location == null || !fileManager.hasLocation(location))
277            return null;
278
279        return fileManager.getJavaFileForInput(location,
280                                               names.module_info.toString(),
281                                               kind);
282    }
283
284    private List<ModuleSymbol> scanModulePath(ModuleSymbol toFind) {
285        ListBuffer<ModuleSymbol> results = new ListBuffer<>();
286        Map<Name, Location> namesInSet = new HashMap<>();
287        boolean multiModuleMode = fileManager.hasLocation(StandardLocation.MODULE_SOURCE_PATH);
288        while (moduleLocationIterator.hasNext()) {
289            Set<Location> locns = (moduleLocationIterator.next());
290            namesInSet.clear();
291            for (Location l: locns) {
292                try {
293                    Name n = names.fromString(fileManager.inferModuleName(l));
294                    if (namesInSet.put(n, l) == null) {
295                        ModuleSymbol msym = syms.enterModule(n);
296                        if (msym.sourceLocation != null || msym.classLocation != null) {
297                            // module has already been found, so ignore this instance
298                            continue;
299                        }
300                        if (fileManager.hasLocation(StandardLocation.PATCH_MODULE_PATH) &&
301                            msym.patchLocation == null) {
302                            msym.patchLocation =
303                                    fileManager.getLocationForModule(StandardLocation.PATCH_MODULE_PATH,
304                                                                     msym.name.toString());
305                            if (msym.patchLocation != null &&
306                                multiModuleMode &&
307                                fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
308                                msym.patchOutputLocation =
309                                        fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
310                                                                         msym.name.toString());
311                            }
312                        }
313                        if (moduleLocationIterator.outer == StandardLocation.MODULE_SOURCE_PATH) {
314                            msym.sourceLocation = l;
315                            if (fileManager.hasLocation(StandardLocation.CLASS_OUTPUT)) {
316                                msym.classLocation =
317                                        fileManager.getLocationForModule(StandardLocation.CLASS_OUTPUT,
318                                                                         msym.name.toString());
319                            }
320                        } else {
321                            msym.classLocation = l;
322                        }
323                        if (moduleLocationIterator.outer == StandardLocation.SYSTEM_MODULES ||
324                            moduleLocationIterator.outer == StandardLocation.UPGRADE_MODULE_PATH) {
325                            msym.flags_field |= Flags.SYSTEM_MODULE;
326                        }
327                        if (toFind == null ||
328                            (toFind == msym && (msym.sourceLocation != null || msym.classLocation != null))) {
329                            // Note: cannot return msym directly, because we must finish
330                            // processing this set first
331                            results.add(msym);
332                        }
333                    } else {
334                        log.error(Errors.DuplicateModuleOnPath(
335                                getDescription(moduleLocationIterator.outer), n));
336                    }
337                } catch (IOException e) {
338                    // skip location for now?  log error?
339                }
340            }
341            if (toFind != null && results.nonEmpty())
342                return results.toList();
343        }
344
345        return results.toList();
346    }
347
348    private void findModuleInfo(ModuleSymbol msym) {
349        try {
350            JavaFileObject fo;
351
352            fo = getModuleInfoFromLocation(msym.patchOutputLocation, Kind.CLASS);
353            fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.CLASS), fo);
354            fo = preferredFileObject(getModuleInfoFromLocation(msym.patchLocation, Kind.SOURCE), fo);
355
356            if (fo == null) {
357                fo = getModuleInfoFromLocation(msym.classLocation, Kind.CLASS);
358                fo = preferredFileObject(getModuleInfoFromLocation(msym.sourceLocation, Kind.SOURCE), fo);
359            }
360
361            if (fo == null) {
362                String moduleName = msym.sourceLocation == null && msym.classLocation != null ?
363                    fileManager.inferModuleName(msym.classLocation) : null;
364                if (moduleName != null) {
365                    msym.module_info.classfile = null;
366                    msym.flags_field |= Flags.AUTOMATIC_MODULE;
367                } else {
368                    msym.kind = ERR;
369                }
370            } else {
371                msym.module_info.classfile = fo;
372                msym.module_info.completer = new Symbol.Completer() {
373                    @Override
374                    public void complete(Symbol sym) throws CompletionFailure {
375                        classFinder.fillIn(msym.module_info);
376                    }
377                    @Override
378                    public String toString() {
379                        return "ModuleInfoCompleter";
380                    }
381                };
382            }
383        } catch (IOException e) {
384            msym.kind = ERR;
385        }
386    }
387
388    private JavaFileObject preferredFileObject(JavaFileObject fo1, JavaFileObject fo2) {
389        if (fo1 == null) return fo2;
390        if (fo2 == null) return fo1;
391        return classFinder.preferredFileObject(fo1, fo2);
392    }
393
394    Fragment getDescription(StandardLocation l) {
395        switch (l) {
396            case MODULE_PATH: return Fragments.LocnModule_path;
397            case MODULE_SOURCE_PATH: return Fragments.LocnModule_source_path;
398            case SYSTEM_MODULES: return Fragments.LocnSystem_modules;
399            case UPGRADE_MODULE_PATH: return Fragments.LocnUpgrade_module_path;
400            default:
401                throw new AssertionError();
402        }
403    }
404
405    public interface ModuleNameFromSourceReader {
406        public Name readModuleName(JavaFileObject file);
407    }
408
409}
410