/*
 * 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.
 *
 * 
 */
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}));
    }
}
