blob: 404a85a558f4b34a0d73ebe926caf20f258cdc4e [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 org.apache.ignite.tools.classgen;
import java.io.BufferedInputStream;
import java.io.Externalizable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.PrintStream;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Comparator;
import java.util.TreeSet;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;
/**
* Serialized classes generator.
*/
public class ClassesGenerator {
/** */
private static final String META_INF = "META-INF/";
/** */
private static final String DEFAULT_FILE_PATH = META_INF + "classnames.properties";
/** */
private static final String[] EXCLUDED_PACKAGES = {
"org.apache.ignite.tools"
};
/** Empty URL array. */
private static final URL[] EMPTY_URL_ARR = new URL[0];
/** Builtin class loader class.
*
* Note: needs for compatibility with Java 9.
*/
private static final Class bltClsLdrCls = defaultClassLoaderClass();
/** Url class loader field.
*
* Note: needs for compatibility with Java 9.
*/
private static final Field urlClsLdrField = urlClassLoaderField();
/**
* @param args Arguments.
* @throws Exception In case of error.
*/
public static void main(String[] args) throws Exception {
assert args.length >= 3;
String basePath = args[0];
String hdr = args[1];
String[] packages = args[2].split(":");
String finaName = args.length == 4 ? args[3] : null;
ClassesGenerator gen = new ClassesGenerator(basePath, hdr, packages, finaName);
gen.generate();
}
/** */
private final Collection<Class> classes = new TreeSet<>(new Comparator<Class>() {
@Override public int compare(Class c1, Class c2) {
return c1.getName().compareTo(c2.getName());
}
});
/** */
private final Collection<String> errs = new ArrayList<>();
/** */
private final String basePath;
/** */
private final String hdr;
/** */
private final String[] packages;
/** */
private final String fileName;
/**
* @param basePath Base file path.
* @param hdr Header.
* @param packages Included packages.
* @param fileName Property file name.
*/
private ClassesGenerator(String basePath, String hdr, String[] packages, String fileName) {
this.basePath = basePath;
this.hdr = hdr;
this.packages = packages;
this.fileName = fileName;
}
/**
* @throws Exception In case of error.
*/
private void generate() throws Exception {
System.out.println("Generating classnames.properties...");
for (URL url : classLoaderUrls(getClass().getClassLoader()))
processUrl(url);
if (!errs.isEmpty()) {
StringBuilder sb = new StringBuilder("Failed to generate classnames.properties due to errors:\n");
for (String err : errs)
sb.append(" ").append(err).append('\n');
String msg = sb.toString().trim();
System.out.println(msg);
throw new Exception(msg);
}
PrintStream out = new PrintStream(new File(basePath,
(fileName == null || fileName.isEmpty()) ? DEFAULT_FILE_PATH : META_INF + fileName));
out.println(hdr);
out.println();
for (Class cls : classes)
out.println(cls.getName());
}
/**
* @param url URL.
* @throws Exception In case of error.
*/
private void processUrl(URL url) throws Exception {
System.out.println(" Processing URL: " + url);
File file = new File(url.toURI());
int prefixLen = file.getPath().length() + 1;
processFile(file, prefixLen);
}
/**
* @param file File.
* @throws Exception In case of error.
*/
private void processFile(File file, int prefixLen) throws Exception {
if (!file.exists())
throw new FileNotFoundException("File doesn't exist: " + file);
if (file.isDirectory()) {
for (File f : file.listFiles())
processFile(f, prefixLen);
}
else {
assert file.isFile();
String path = file.getPath();
if (path.toLowerCase().endsWith(".jar")) {
try (JarInputStream jin = new JarInputStream(new BufferedInputStream(new FileInputStream(path)))) {
JarEntry entry;
while ((entry = jin.getNextJarEntry()) != null) {
if (!entry.isDirectory() && entry.getName().toLowerCase().endsWith(".class"))
processClassFile(entry.getName(), 0);
}
}
}
else if (path.toLowerCase().endsWith(".class"))
processClassFile(path, prefixLen);
}
}
/**
* @param clsLdr Class loader.
* @return URLs of class loader.
*/
public static URL[] classLoaderUrls(ClassLoader clsLdr) {
if (clsLdr == null)
return EMPTY_URL_ARR;
else if (clsLdr instanceof URLClassLoader)
return ((URLClassLoader)clsLdr).getURLs();
else if (bltClsLdrCls != null && urlClsLdrField != null && bltClsLdrCls.isAssignableFrom(clsLdr.getClass())) {
try {
return ((URLClassLoader)urlClsLdrField.get(clsLdr)).getURLs();
}
catch (IllegalAccessException e) {
return EMPTY_URL_ARR;
}
}
else
return EMPTY_URL_ARR;
}
/** */
private static Class defaultClassLoaderClass() {
try {
return Class.forName("jdk.internal.loader.BuiltinClassLoader");
}
catch (ClassNotFoundException e) {
return null;
}
}
/** */
private static Field urlClassLoaderField() {
try {
Class cls = defaultClassLoaderClass();
return cls == null ? null : cls.getDeclaredField("ucp");
}
catch (NoSuchFieldException e) {
return null;
}
}
/**
* @param path File path.
* @param prefixLen Prefix length.
* @throws Exception In case of error.
*/
private void processClassFile(String path, int prefixLen)
throws Exception {
String clsName = path.substring(prefixLen, path.length() - 6).replace(File.separatorChar, '.');
for (String pkg : EXCLUDED_PACKAGES) {
if (clsName.startsWith(pkg))
return;
}
boolean included = false;
for (String pkg : packages) {
if (clsName.startsWith(pkg)) {
included = true;
break;
}
}
if (included) {
Class<?> cls = Class.forName(clsName, false, getClass().getClassLoader());
if (Serializable.class.isAssignableFrom(cls) &&
!(cls.getName().endsWith("Future") || cls.getName().endsWith("FutureAdapter"))) {
if (!cls.isInterface() && !Modifier.isAbstract(cls.getModifiers()) && !cls.isEnum() &&
!cls.getSimpleName().isEmpty()) {
try {
Field field = cls.getDeclaredField("serialVersionUID");
if (!field.getType().equals(long.class))
errs.add("serialVersionUID field is not long in class: " + cls.getName());
int mod = field.getModifiers();
if (!Modifier.isStatic(mod))
errs.add("serialVersionUID field is not static in class: " + cls.getName());
if (!Modifier.isFinal(mod))
errs.add("serialVersionUID field is not final in class: " + cls.getName());
}
catch (NoSuchFieldException ignored) {
errs.add("No serialVersionUID field in class: " + cls.getName());
}
if (Externalizable.class.isAssignableFrom(cls)) {
try {
Constructor<?> cons = cls.getConstructor();
if (!Modifier.isPublic(cons.getModifiers()))
errs.add("Default constructor in Externalizable class is not public: " + cls.getName());
}
catch (NoSuchMethodException ignored) {
errs.add("No default constructor in Externalizable class: " + cls.getName());
}
}
}
classes.add((Class)cls);
}
}
}
}