/*
* 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 com.sun.jini.tool;
import java.io.File;
import java.io.Serializable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ResourceBundle;
import java.util.MissingResourceException;
import java.text.MessageFormat;
/**
* Tool to check for serializable classes that do not have explicit
* <code>serialVersionUID</code> fields.
*
* @author Sun Microsystems, Inc.
*/
public class CheckSer {
private static final int MASK = Modifier.STATIC | Modifier.FINAL;
/**
* Checks class file directory hierarchies for serializable classes
* that do not have explicit <code>serialVersionUID</code> fields,
* and prints the names of such classes to the standard output stream.
* The only options are zero or more filenames that specify the roots
* of directory hierarchies; if no filenames are specified, the single
* root <code>/vob/jive/classes</code> is used. In those hierarchies,
* each file with a name ending in the suffix <code>.class</code> is
* treated as a class file; the corresponding class name is obtained
* from the filename by stripping off both the original prefix root
* filename and the <code>.class</code> suffix, and replacing each file
* separator character with a period (<code>.</code>). Each such class
* is loaded from the class loader of this tool. If the class is not an
* interface, directly or indirectly implements {@link Serializable},
* and does not have a declared <code>static</code> <code>final</code>
* field named <code>serialVersionUID</code>, then the name of the class
* is printed to the standard output stream.
*
* @param args the roots of directory hierarchies
*/
public static void main(String[] args) {
if (args.length == 0)
args = new String[]{"/vob/jive/classes"};
for (int i = 0; i < args.length; i++) {
check(args[i], args[i].length() + 1);
}
}
/**
* Checks the class file directory hierarchy starting from the specified
* directory. In the hierarchy, each file with a name ending in the
* suffix <code>.class</code> is treated as a class file; the
* corresponding class name is obtained from the filename by stripping
* off the first <code>strip</code> characters of prefix and the
* <code>.class</code> suffix, and replacing each file separator
* character with a period (<code>.</code>). Each such class is loaded
* from the class loader of this tool. If the class is not an interface,
* directly or indirectly implements {@link Serializable}, and does not
* have a declared <code>static</code> <code>final</code> field named
* <code>serialVersionUID</code>, then the name of the class is printed
* to the standard output stream.
*
* @param dir directory hierarchy root
* @param strip number of characters of prefix to strip from each
* class file name
*/
public static void check(String dir, int strip) {
String[] files = new File(dir).list();
if (files == null) {
print("checkser.nodir", dir);
return;
}
for (int i = 0; i < files.length; i++) {
String file = dir + File.separatorChar + files[i];
if (file.endsWith(".class"))
checkClass(file, strip);
else if (new File(file).isDirectory())
check(file, strip);
}
}
private static void checkClass(String file, int strip) {
file = file.substring(strip, file.length() - 6);
file = file.replace(File.separatorChar, '.');
try {
Class c = Class.forName(file);
if (c.isInterface() || !Serializable.class.isAssignableFrom(c))
return;
Field f = c.getDeclaredField("serialVersionUID");
if ((f.getModifiers() & MASK) != MASK)
System.out.println(file);
} catch (ClassNotFoundException e) {
print("checkser.failed", file);
} catch (NoClassDefFoundError e) {
print("checkser.failed", file);
} catch (NoSuchFieldException e) {
System.out.println(file);
}
}
private static ResourceBundle resources;
private static boolean resinit = false;
private static synchronized String getString(String key) {
if (!resinit) {
try {
resources = ResourceBundle.getBundle(
"com.sun.jini.tool.resources.checkser");
resinit = true;
} catch (MissingResourceException e) {
e.printStackTrace();
}
}
try {
return resources.getString(key);
} catch (MissingResourceException e) {
return null;
}
}
private static void print(String key, String val) {
String fmt = getString(key);
if (fmt == null)
fmt = "no text found: \"" + key + "\" {0}";
System.out.println(MessageFormat.format(fmt, new String[]{val}));
}
}