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