1 /*
2  * Copyright (c) 2014, 2021, 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.sjavac.comp;
27 
28 import java.nio.file.Path;
29 import java.nio.file.Paths;
30 import java.util.HashSet;
31 import java.util.Iterator;
32 import java.util.Set;
33 
34 import javax.tools.JavaFileObject;
35 
36 import com.sun.source.tree.CompilationUnitTree;
37 import com.sun.source.util.TaskEvent;
38 import com.sun.source.util.TaskListener;
39 import com.sun.tools.javac.tree.JCTree;
40 import com.sun.tools.javac.tree.JCTree.JCFieldAccess;
41 import com.sun.tools.javac.tree.JCTree.JCIdent;
42 import com.sun.tools.javac.util.DefinedBy;
43 import com.sun.tools.javac.util.DefinedBy.Api;
44 import com.sun.tools.javac.util.Name;
45 import com.sun.tools.sjavac.Log;
46 
47 public class PathAndPackageVerifier implements TaskListener {
48 
49     // Stores the set of compilation units whose source file path does not
50     // match the package declaration.
51     Set<CompilationUnitTree> misplacedCompilationUnits = new HashSet<>();
52 
53     @Override
54     @DefinedBy(Api.COMPILER_TREE)
finished(TaskEvent e)55     public void finished(TaskEvent e) {
56         if (e.getKind() == TaskEvent.Kind.ANALYZE) {
57 
58             CompilationUnitTree cu = e.getCompilationUnit();
59             if (cu == null)
60                 return;
61 
62             JavaFileObject jfo = cu.getSourceFile();
63             if (jfo == null)
64                 return; // No source file -> package doesn't matter
65 
66             JCTree pkg = (JCTree) cu.getPackageName();
67             if (pkg == null)
68                 return; // Default package. See JDK-8048144.
69 
70             Path dir = Paths.get(jfo.toUri()).normalize().getParent();
71             if (!checkPathAndPackage(dir, pkg))
72                 misplacedCompilationUnits.add(cu);
73         }
74 
75         if (e.getKind() == TaskEvent.Kind.COMPILATION) {
76             for (CompilationUnitTree cu : misplacedCompilationUnits) {
77                 Log.error("Misplaced compilation unit.");
78                 Log.error("    Directory: " + Paths.get(cu.getSourceFile().toUri()).getParent());
79                 Log.error("    Package:   " + cu.getPackageName());
80             }
81         }
82     }
83 
errorsDiscovered()84     public boolean errorsDiscovered() {
85         return misplacedCompilationUnits.size() > 0;
86     }
87 
88     /* Returns true if dir matches pkgName.
89      *
90      * Examples:
91      *     (a/b/c, a.b.c) gives true
92      *     (i/j/k, i.x.k) gives false
93      *
94      * Currently (x/a/b/c, a.b.c) also gives true. See JDK-8059598.
95      */
checkPathAndPackage(Path dir, JCTree pkgName)96     private boolean checkPathAndPackage(Path dir, JCTree pkgName) {
97         Iterator<String> pathIter = new ParentIterator(dir);
98         Iterator<String> pkgIter = new EnclosingPkgIterator(pkgName);
99         while (pathIter.hasNext() && pkgIter.hasNext()) {
100             if (!pathIter.next().equals(pkgIter.next()))
101                 return false;
102         }
103         return !pkgIter.hasNext(); /*&& !pathIter.hasNext() See JDK-8059598 */
104     }
105 
106     /* Iterates over the names of the parents of the given path:
107      * Example: dir1/dir2/dir3  results in  dir3 -> dir2 -> dir1
108      */
109     private static class ParentIterator implements Iterator<String> {
110         Path next;
ParentIterator(Path initial)111         ParentIterator(Path initial) {
112             next = initial;
113         }
114         @Override
hasNext()115         public boolean hasNext() {
116             return next != null;
117         }
118         @Override
next()119         public String next() {
120             String tmp = next.getFileName().toString();
121             next = next.getParent();
122             return tmp;
123         }
124     }
125 
126     /* Iterates over the names of the enclosing packages:
127      * Example: pkg1.pkg2.pkg3  results in  pkg3 -> pkg2 -> pkg1
128      */
129     private static class EnclosingPkgIterator implements Iterator<String> {
130         JCTree next;
EnclosingPkgIterator(JCTree initial)131         EnclosingPkgIterator(JCTree initial) {
132             next = initial;
133         }
134         @Override
hasNext()135         public boolean hasNext() {
136             return next != null;
137         }
138         @Override
next()139         public String next() {
140             Name name;
141             if (next instanceof JCIdent identNext) {
142                 name = identNext.name;
143                 next = null;
144             } else {
145                 JCFieldAccess fa = (JCFieldAccess) next;
146                 name = fa.name;
147                 next = fa.selected;
148             }
149             return name.toString();
150         }
151     }
152 }
153