Deauthorize.java

/*
 * 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.creadur.tentacles;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;

import org.codehaus.swizzle.stream.DelimitedTokenReplacementInputStream;
import org.codehaus.swizzle.stream.ExcludeFilterInputStream;
import org.codehaus.swizzle.stream.StringTokenHandler;

/**
 * Little utility that will yank the author comments from java files.
 * 
 * If the resulting comment block is effectively empty, it will be yanked too.
 */
public class Deauthorize {

	/**
	 * All input must be valid directories.
	 * 
	 * Invalid input is logged to System.err and skipped
	 * 
	 * @param args
	 *            a list of directories to scan and fix
	 * @throws Exception
	 *             in case of errors.
	 */
	public static void main(final String[] args) throws Exception {

		if (args.length == 0) {
			throw new IllegalArgumentException(
					"At least one directory must be specified");
		}

		final List<File> dirs = new ArrayList<>();

		// Check the input args upfront
		for (final String arg : args) {
			final File dir = new File(arg);

			if (not(dir.exists(), "Does not exist: %s", arg)) {
				continue;
			}
			if (not(dir.isDirectory(), "Not a directory: %s", arg)) {
				continue;
			}

			dirs.add(dir);
		}

		// Exit if we got bad input
		if (dirs.size() != args.length) {
			System.exit(1);
		}

		// Go!
		for (final File dir : dirs) {
			deauthorize(dir);
		}
	}

	/**
	 * Iterate over all the java files in the given directory
	 * 
	 * Read in the file so we can guess the line ending -- if we didn't need to
	 * do that we could just stream. Run the content through Swizzle Stream and
	 * filter out any author tags as well as any comment blocks that wind up (or
	 * already were) empty as a result.
	 * 
	 * If that had any effect on the contents of the file, write it back out.
	 * 
	 * Should skip any files that are not readable or writable.
	 * 
	 * Will log an error on System.err for any files that were updated and were
	 * not writable. Files that are not writable and don't need updating are
	 * simply ignored.
	 * 
	 * @param dir
	 * @throws IOException
	 */
	private static void deauthorize(final File dir) throws IOException {
		deauthorize(dir, new IOSystem());
	}

	private static void deauthorize(final File dir, final IOSystem io)
			throws IOException {
		for (final File file : new FileSystem().collect(dir, ".*\\.java")) {

			if (not(file.canRead(), "File not readable: %s",
					file.getAbsolutePath())) {
				continue;
			}

			final String text = io.slurp(file);

			// You really can't trust text to be in the native line ending
			final String eol = text.contains("\r\n") ? "\r\n" : "\n";
			final String startComment = eol + "/*";
			final String endComment = "*/" + eol;

			final InputStream baseIn = new ByteArrayInputStream(text.getBytes());

			// Yank author tags
			InputStream in = new ExcludeFilterInputStream(baseIn, " * @author",
					eol);

			// Clean "empty" comments
			in = new DelimitedTokenReplacementInputStream(in, startComment,
					endComment, new StringTokenHandler() {
						@Override
						public String handleToken(final String commentBlock) {

							// Yank if empty
							if (commentBlock.replaceAll("[\\s*]", "").isEmpty()) {
								return eol;
							}

							// Keep otherwise
							return startComment + commentBlock + endComment;
						}
					});

			final byte[] content = io.read(in);

			if (content.length != file.length()) {

				if (not(file.canWrite(), "File not writable: %s",
						file.getAbsolutePath())) {
					continue;
				}

				io.copy(content, file);
			}
		}

	}

	private static boolean not(boolean b, final String message,
			final Object... details) {
		b = !b;
		if (b) {
			System.err.printf(message, details);
			System.err.println();
		}
		return b;
	}
}