blob: 0c2c92f56cdb3c25aabb2b0cf7c885c2694eda16 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package javax.el;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.net.URL;
import java.util.Enumeration;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Stream;
import org.junit.Assert;
import org.junit.Test;
import org.apache.tomcat.util.compat.JreCompat;
public class TestImportHandlerStandardPackages {
@Test
public void testClassListsAreComplete() throws Exception {
// Use reflection to get hold of the internal Map
Class<?> clazz = ImportHandler.class;
Field f = clazz.getDeclaredField("standardPackages");
f.setAccessible(true);
Object obj = f.get(null);
Assert.assertTrue("Not Map", obj instanceof Map);
@SuppressWarnings("unchecked")
Map<String,Set<String>> standardPackageName = (Map<String, Set<String>>) obj;
for (Map.Entry<String,Set<String>> entry : standardPackageName.entrySet()) {
checkPackageClassList(entry.getKey(), entry.getValue());
}
}
private void checkPackageClassList(String packageName, Set<String> classNames) throws Exception {
if ("java.lang".equals(packageName)) {
// The code below is designed to run on Java 9 so skip this check
// if running on Java 8. The test has previously been run with Java
// 9 (and later) so it is not necessary that this is executed on
// every test run. The intention is that it will catch new classes
// when the tests are run on a newer JRE.
// The latest versions of the JRE where this test is known to pass are
// - OpenJDK 11.0.1
// - OpenJDK 12 EA 26
// - OpenJDK 13 EA 02
if (!JreCompat.isJre9Available()) {
return;
}
getJavaBaseClasses().filter(c -> (c.startsWith("java/lang/")))
.filter(c -> c.lastIndexOf('/') == 9) // Exclude sub-packages
.filter(c -> c.endsWith(".class")) // Exclude non-class resources
.map(c -> c.substring(10, c.length() - 6)) // Extract class name
.map(c-> {
try {
return Class.forName("java.lang." + c); // Get the class object
} catch (ClassNotFoundException e) {
throw new RuntimeException();
}
})
.filter(c -> Modifier.isPublic(c.getModifiers())) // Exclude non-public classes
.map(c -> c.getName().substring(10)) // Back to the class name
.map(c -> c.replace('$', '.'))
.filter(c -> !classNames.contains(c)) // Skip classes already listed
.filter(c -> !c.startsWith("FdLibm.")) // Skip public inner class
.filter(c -> !c.startsWith("LiveStackFrame.")) // Skip public inner class
.filter(c -> !c.startsWith("WeakPairMap.")) // Skip public inner class
.forEach(c -> Assert.fail("java.lang." + c)); // Should have in list
} else {
// When this test runs, the class loader will be loading resources
// from a directory for each of these packages.
ClassLoader cl = Thread.currentThread().getContextClassLoader();
String path = packageName.replace('.', '/');
Enumeration<URL> resources = cl.getResources(path);
while (resources.hasMoreElements()) {
URL resource = resources.nextElement();
// Debugging for Gump failure
System.out.println("Scanning: [" + resource + "]");
URI uri = resource.toURI();
// Gump includes some JARs on classpath - skip them
if (!"file".equals(uri.getScheme())) {
continue;
}
File dir = new File(uri);
String[] files = dir.list();
Assert.assertNotNull(files);
for (String file : files) {
if (!file.endsWith(".class")) {
// Skip non-class resoucres
continue;
}
if (file.startsWith("Test")) {
// Skip test resources
continue;
}
if (file.matches(".*\\$[0-9]?\\.class")) {
// Skip anonymous inner classes
continue;
}
String name = file.substring(0, file.length() - 6);
name = name.replace('$', '.');
if (classNames.contains(name)) {
// Skip classes already known
continue;
}
File f = new File (dir, file);
if (!f.isFile()) {
// Skip directories
continue;
}
Class<?> clazz = Class.forName(packageName + "." + name);
if (!Modifier.isPublic(clazz.getModifiers())) {
// Skip non-public classes
continue;
}
// There should be nothing left unless we missed something
Assert.fail(packageName + "." + name);
}
}
}
}
private static Stream<String> getJavaBaseClasses() throws Exception {
// While this code is only used on Java 9 and later, it needs to compile
// with Java 8 so use reflection for now.
Class<?> clazzModuleFinder = Class.forName("java.lang.module.ModuleFinder");
Class<?> clazzModuleReference = Class.forName("java.lang.module.ModuleReference");
Class<?> clazzModuleReader = Class.forName("java.lang.module.ModuleReader");
// Returns ModuleFinder
Object mf = clazzModuleFinder.getMethod("ofSystem").invoke(null);
// Returns ModuleReference
Object mRef = ((Optional<?>) clazzModuleFinder.getMethod(
"find", String.class).invoke(mf, "java.base")).get();
// Returns ModuleReader
Object mr = clazzModuleReference.getMethod("open").invoke(mRef);
// Returns the contents of the module
@SuppressWarnings("unchecked")
Stream<String> result = (Stream<String>) clazzModuleReader.getMethod("list").invoke(mr);
return result;
}
}