Showing posts with label Utility. Show all posts
Showing posts with label Utility. Show all posts

Saturday, September 15, 2007

CalendarUtil

Useful calendar utilities

An utility class with some fairly simple methods and some very useful methods. The elapsedXxxx methods are absolutely useful. If you want to use it with java.util.Date or even java.lang.String objects, then you may find the DateUtil more useful.

package net.balusc.util;

import java.util.Calendar;

/**
 * Useful Calendar utilities.
 *
 * @author BalusC
 * @link http://balusc.omnifaces.org/2007/09/calendarutil.html
 */
public final class CalendarUtil {

    // Init ---------------------------------------------------------------------------------------

    private CalendarUtil() {
        // Utility class, hide the constructor.
    }

    // Validators ---------------------------------------------------------------------------------

    /**
     * Checks whether the given day, month and year combination is a valid date or not.
     * @param year The year part of the date.
     * @param month The month part of the date.
     * @param day The day part of the date.
     * @return True if the given day, month and year combination is a valid date.
     */
    public static boolean isValidDate(int year, int month, int day) {
        return isValidDate(year, month, day, 0, 0, 0);
    }

    /**
     * Checks whether the given hour, minute and second combination is a valid time or not.
     * @param hour The hour part of the time.
     * @param minute The minute part of the time.
     * @param second The second part of the time.
     * @return True if the given hour, minute and second combination is a valid time.
     */
    public static boolean isValidTime(int hour, int minute, int second) {
        return isValidDate(1, 1, 1, hour, minute, second);
    }

    /**
     * Checks whether the given day, month, year, hour, minute and second combination is a valid
     * date or not.
     * @param year The year part of the date.
     * @param month The month part of the date.
     * @param day The day part of the date.
     * @param hour The hour part of the date.
     * @param minute The minute part of the date.
     * @param second The second part of the date.
     * @return True if the given day, month, year, hour, minute and second combination is a valid
     * date.
     */
    public static boolean isValidDate(
        int year, int month, int day, int hour, int minute, int second)
    {
        try {
            getValidCalendar(year, month, day, hour, minute, second);
            return true;
        } catch (IllegalArgumentException e) {
            return false;
        }
    }

    /**
     * Validate the actual date of the given date elements and returns a calendar instance based on
     * the given date elements. The time is forced to 00:00:00.
     * @param year The year part of the date.
     * @param month The month part of the date.
     * @param day The day part of the date.
     * @return A Calendar instance prefilled with the given date elements.
     * @throws IllegalArgumentException If the given date elements does not represent a valid date.
     */
    public static Calendar getValidCalendar(int year, int month, int day) {
        return getValidCalendar(year, month, day, 0, 0, 0);
    }

    /**
     * Validate the actual date of the given date elements and returns a calendar instance based on
     * the given date elements.
     * @param year The year part of the date.
     * @param month The month part of the date.
     * @param day The day part of the date.
     * @param hour The hour part of the date.
     * @param minute The minute part of the date.
     * @param second The second part of the date.
     * @return A Calendar instance prefilled with the given date elements.
     * @throws IllegalArgumentException If the given date elements does not represent a valid date.
     */
    public static Calendar getValidCalendar(
        int year, int month, int day, int hour, int minute, int second)
    {
        Calendar calendar = Calendar.getInstance();
        calendar.clear();
        calendar.setLenient(false); // Don't automatically convert invalid date.
        calendar.set(year, month - 1, day, hour, minute, second);
        calendar.getTimeInMillis(); // Lazy update, throws IllegalArgumentException if invalid date.
        return calendar;
    }

    // Changers -----------------------------------------------------------------------------------

    /**
     * Add the given amount of years to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of years to.
     * @param years The amount of years to be added to the given calendar. Negative values are also
     * allowed, it will just go back in time.
     */
    public static void addYears(Calendar calendar, int years) {
        calendar.add(Calendar.YEAR, years);
    }

    /**
     * Add the given amount of months to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of months to.
     * @param months The amount of months to be added to the given calendar. Negative values are
     * also allowed, it will just go back in time.
     */
    public static void addMonths(Calendar calendar, int months) {
        calendar.add(Calendar.MONTH, months);
    }

    /**
     * Add the given amount of days to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of days to.
     * @param days The amount of days to be added to the given calendar. Negative values are also
     * allowed, it will just go back in time.
     */
    public static void addDays(Calendar calendar, int days) {
        calendar.add(Calendar.DATE, days);
    }

    /**
     * Add the given amount of hours to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of hours to.
     * @param hours The amount of hours to be added to the given calendar. Negative values are also
     * allowed, it will just go back in time.
     */
    public static void addHours(Calendar calendar, int hours) {
        calendar.add(Calendar.HOUR, hours);
    }

    /**
     * Add the given amount of minutes to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of minutes to.
     * @param minutes The amount of minutes to be added to the given calendar. Negative values are
     * also allowed, it will just go back in time.
     */
    public static void addMinutes(Calendar calendar, int minutes) {
        calendar.add(Calendar.MINUTE, minutes);
    }

    /**
     * Add the given amount of seconds to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of seconds to.
     * @param seconds The amount of seconds to be added to the given calendar. Negative values are
     * also allowed, it will just go back in time.
     */
    public static void addSeconds(Calendar calendar, int seconds) {
        calendar.add(Calendar.SECOND, seconds);
    }

    /**
     * Add the given amount of millis to the given calendar. The changes are reflected in the given
     * calendar.
     * @param calendar The calendar to add the given amount of millis to.
     * @param millis The amount of millis to be added to the given calendar. Negative values are
     * also allowed, it will just go back in time.
     */
    public static void addMillis(Calendar calendar, int millis) {
        calendar.add(Calendar.MILLISECOND, millis);
    }

    // Comparators --------------------------------------------------------------------------------

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year.
     */
    public static boolean sameYear(Calendar one, Calendar two) {
        return one.get(Calendar.YEAR) == two.get(Calendar.YEAR);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year and month.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year and month.
     */
    public static boolean sameMonth(Calendar one, Calendar two) {
        return one.get(Calendar.MONTH) == two.get(Calendar.MONTH) && sameYear(one, two);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year, month and day.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year, month and day.
     */
    public static boolean sameDay(Calendar one, Calendar two) {
        return one.get(Calendar.DATE) == two.get(Calendar.DATE) && sameMonth(one, two);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year, month, day and
     * hour.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year, month, day and hour.
     */
    public static boolean sameHour(Calendar one, Calendar two) {
        return one.get(Calendar.HOUR_OF_DAY) == two.get(Calendar.HOUR_OF_DAY) && sameDay(one, two);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year, month, day,
     * hour and minute.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year, month, day, hour and
     * minute.
     */
    public static boolean sameMinute(Calendar one, Calendar two) {
        return one.get(Calendar.MINUTE) == two.get(Calendar.MINUTE) && sameHour(one, two);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same year, month, day,
     * hour, minute and second.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same year, month, day, hour, minute
     * and second.
     */
    public static boolean sameSecond(Calendar one, Calendar two) {
        return one.get(Calendar.SECOND) == two.get(Calendar.SECOND) && sameMinute(one, two);
    }

    /**
     * Returns <tt>true</tt> if the two given calendars are dated on the same time. The difference
     * from <tt>one.equals(two)</tt> is that this method does not respect the time zone.
     * @param one The one calendar.
     * @param two The other calendar.
     * @return True if the two given calendars are dated on the same time.
     */
    public static boolean sameTime(Calendar one, Calendar two) {
        return one.getTimeInMillis() == two.getTimeInMillis();
    }

    // Calculators --------------------------------------------------------------------------------

    /**
     * Retrieve the amount of elapsed years between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed years between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedYears(Calendar before, Calendar after) {
        return elapsed(before, after, Calendar.YEAR);
    }

    /**
     * Retrieve the amount of elapsed months between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed months between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedMonths(Calendar before, Calendar after) {
        return elapsed(before, after, Calendar.MONTH);
    }

    /**
     * Retrieve the amount of elapsed days between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed days between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedDays(Calendar before, Calendar after) {
        return elapsed(before, after, Calendar.DATE);
    }

    /**
     * Retrieve the amount of elapsed hours between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed hours between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedHours(Calendar before, Calendar after) {
        return (int) elapsedMillis(before, after, 3600000); // 1h = 60m = 3600s = 3600000ms
    }

    /**
     * Retrieve the amount of elapsed minutes between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed minutes between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedMinutes(Calendar before, Calendar after) {
        return (int) elapsedMillis(before, after, 60000); // 1m = 60s = 60000ms
    }

    /**
     * Retrieve the amount of elapsed seconds between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed seconds between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int elapsedSeconds(Calendar before, Calendar after) {
        return (int) elapsedMillis(before, after, 1000); // 1sec = 1000ms.
    }

    /**
     * Retrieve the amount of elapsed milliseconds between the two given calendars.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The amount of elapsed milliseconds between the two given calendars.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static long elapsedMillis(Calendar before, Calendar after) {
        return elapsedMillis(before, after, 1); // 1ms is apparently 1ms.
    }

    /**
     * Calculate the total of elapsed time from years up to seconds between the two given calendars.
     * It returns an int array with the elapsed years, months, days, hours, minutes and seconds
     * respectively.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @return The elapsed time between the two given calendars in years, months, days, hours,
     * minutes and seconds.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    public static int[] elapsedTime(Calendar before, Calendar after) {
        int[] elapsedTime = new int[6];
        Calendar clone = (Calendar) before.clone(); // Otherwise changes are been reflected.

        elapsedTime[0] = elapsedYears(clone, after);
        addYears(clone, elapsedTime[0]);

        elapsedTime[1] = elapsedMonths(clone, after);
        addMonths(clone, elapsedTime[1]);

        elapsedTime[2] = elapsedDays(clone, after);
        addDays(clone, elapsedTime[2]);

        elapsedTime[3] = elapsedHours(clone, after);
        addHours(clone, elapsedTime[3]);

        elapsedTime[4] = elapsedMinutes(clone, after);
        addMinutes(clone, elapsedTime[4]);

        elapsedTime[5] = elapsedSeconds(clone, after);

        return elapsedTime;
    }

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Retrieve the amount of elapsed time between the two given calendars based on the given
     * calendar field as definied in the Calendar constants, e.g. <tt>Calendar.MONTH</tt>.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @param field The calendar field as definied in the Calendar constants.
     * @return The amount of elapsed time between the two given calendars based on the given
     * calendar field.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    private static int elapsed(Calendar before, Calendar after, int field) {
        checkBeforeAfter(before, after);
        Calendar clone = (Calendar) before.clone(); // Otherwise changes are been reflected.
        int elapsed = -1;
        while (!clone.after(after)) {
            clone.add(field, 1);
            elapsed++;
        }
        return elapsed;
    }

    /**
     * Retrieve the amount of elapsed milliseconds between the two given calendars and directly
     * divide the outcome by the given factor. E.g.: if the division factor is 1, then you will get
     * the elapsed milliseconds unchanged; if the division factor is 1000, then the elapsed
     * milliseconds will be divided by 1000, resulting in the amount of elapsed seconds.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @param factor The division factor which to divide the milliseconds with, expected to be at
     * least 1.
     * @return The amount of elapsed milliseconds between the two given calendars, divided by the
     * given factor.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar or
     * if the division factor is less than 1.
     */
    private static long elapsedMillis(Calendar before, Calendar after, int factor) {
        checkBeforeAfter(before, after);
        if (factor < 1) {
            throw new IllegalArgumentException(
                "Division factor '" + factor + "' should not be less than 1.");
        }
        return (after.getTimeInMillis() - before.getTimeInMillis()) / factor;
    }

    /**
     * Check if the first calendar is actually dated before the second calendar.
     * @param before The first calendar with expected date before the second calendar.
     * @param after The second calendar with expected date after the first calendar.
     * @throws IllegalArgumentException If the first calendar is dated after the second calendar.
     */
    private static void checkBeforeAfter(Calendar before, Calendar after) {
        if (before.after(after)) {
            throw new IllegalArgumentException(
                "The first calendar should be dated before the second calendar.");
        }
    }

}

Copyright - WTFPL

(C) September 2007, BalusC

Monday, September 10, 2007

FileUtil

Useful filesystem level utilities

I'd like to share this utility class with lot of useful filesystem level utility methods. It minimizes the effort of the developer to write code for java.io stuff. Just call the utility method in a try-catch-finally block and handle the eventual IOException to your taste.

/*
 * net/balusc/util/FileUtil.java
 * 
 * Copyright (C) 2007 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.util;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.CharArrayReader;
import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;

/**
 * Useful file system level utilities.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/09/FileUtil.html
 */
public final class FileUtil {

    // Init ---------------------------------------------------------------------------------------

    private static final String LINE_SEPARATOR = System.getProperty("line.separator");

    // Constructors -------------------------------------------------------------------------------

    private FileUtil() {
        // Utility class, hide constructor.
    }

    // Writers ------------------------------------------------------------------------------------

    /**
     * Write byte array to file. If file already exists, it will be overwritten.
     * @param file The file where the given byte array have to be written to.
     * @param bytes The byte array which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, byte[] bytes) throws IOException {
        write(file, new ByteArrayInputStream(bytes), false);
    }

    /**
     * Write byte array to file with option to append to file or not. If not, then any existing
     * file will be overwritten.
     * @param file The file where the given byte array have to be written to.
     * @param bytes The byte array which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, byte[] bytes, boolean append) throws IOException {
        write(file, new ByteArrayInputStream(bytes), append);
    }

    /**
     * Write byte inputstream to file. If file already exists, it will be overwritten.It's highly
     * recommended to feed the inputstream as BufferedInputStream or ByteArrayInputStream as those
     * are been automatically buffered.
     * @param file The file where the given byte inputstream have to be written to.
     * @param input The byte inputstream which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, InputStream input) throws IOException {
        write(file, input, false);
    }

    /**
     * Write byte inputstream to file with option to append to file or not. If not, then any
     * existing file will be overwritten. It's highly recommended to feed the inputstream as
     * BufferedInputStream or ByteArrayInputStream as those are been automatically buffered.
     * @param file The file where the given byte inputstream have to be written to.
     * @param input The byte inputstream which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, InputStream input, boolean append) throws IOException {
        mkdirs(file);
        BufferedOutputStream output = null;

        try {
            output = new BufferedOutputStream(new FileOutputStream(file, append));
            int data = -1;
            while ((data = input.read()) != -1) {
                output.write(data);
            }
        } finally {
            close(input, file);
            close(output, file);
        }
    }

    /**
     * Write character array to file. If file already exists, it will be overwritten.
     * @param file The file where the given character array have to be written to.
     * @param chars The character array which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, char[] chars) throws IOException {
        write(file, new CharArrayReader(chars), false);
    }

    /**
     * Write character array to file with option to append to file or not. If not, then any
     * existing file will be overwritten.
     * @param file The file where the given character array have to be written to.
     * @param chars The character array which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, char[] chars, boolean append) throws IOException {
        write(file, new CharArrayReader(chars), append);
    }

    /**
     * Write string value to file. If file already exists, it will be overwritten.
     * @param file The file where the given string value have to be written to.
     * @param string The string value which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, String string) throws IOException {
        write(file, new CharArrayReader(string.toCharArray()), false);
    }

    /**
     * Write string value to file with option to append to file or not. If not, then any existing
     * file will be overwritten.
     * @param file The file where the given string value have to be written to.
     * @param string The string value which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, String string, boolean append) throws IOException {
        write(file, new CharArrayReader(string.toCharArray()), append);
    }

    /**
     * Write character reader to file. If file already exists, it will be overwritten. It's highly
     * recommended to feed the reader as BufferedReader or CharArrayReader as those are been
     * automatically buffered.
     * @param file The file where the given character reader have to be written to.
     * @param reader The character reader which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, Reader reader) throws IOException {
        write(file, reader, false);
    }

    /**
     * Write character reader to file with option to append to file or not. If not, then any
     * existing file will be overwritten. It's highly recommended to feed the reader as
     * BufferedReader or CharArrayReader as those are been automatically buffered.
     * @param file The file where the given character reader have to be written to.
     * @param reader The character reader which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, Reader reader, boolean append) throws IOException {
        mkdirs(file);
        BufferedWriter writer = null;

        try {
            writer = new BufferedWriter(new FileWriter(file, append));
            int data = -1;
            while ((data = reader.read()) != -1) {
                writer.write(data);
            }
        } finally {
            close(reader, file);
            close(writer, file);
        }
    }

    /**
     * Write list of String records to file. If file already exists, it will be overwritten.
     * @param file The file where the given character reader have to be written to.
     * @param records The list of String records which have to be written to the given file.
     * @throws IOException If writing file fails.
     */
    public static void write(File file, List<String> records) throws IOException {
        write(file, records, false);
    }

    /**
     * Write list of String records to file with option to append to file or not. If not, then any
     * existing file will be overwritten.
     * @param file The file where the given character reader have to be written to.
     * @param records The list of String records which have to be written to the given file.
     * @param append Append to file?
     * @throws IOException If writing file fails.
     */
    public static void write(File file, List<String> records, boolean append) throws IOException {
        mkdirs(file);
        BufferedWriter writer = null;

        try {
            writer = new BufferedWriter(new FileWriter(file, append));
            for (String record : records) {
                writer.write(record);
                writer.write(LINE_SEPARATOR);
            }
        } finally {
            close(writer, file);
        }
    }

    // Readers ------------------------------------------------------------------------------------

    /**
     * Read byte array from file. Take care with big files, this would be memory hogging, rather
     * use readStream() instead.
     * @param file The file to read the byte array from.
     * @return The byte array with the file contents.
     * @throws IOException If reading file fails.
     */
    public static byte[] readBytes(File file) throws IOException {
        BufferedInputStream stream = (BufferedInputStream) readStream(file);
        byte[] bytes = new byte[stream.available()];
        stream.read(bytes);
        return bytes;
    }

    /**
     * Read byte stream from file.
     * @param file The file to read the byte stream from.
     * @return The byte stream with the file contents (actually: BufferedInputStream).
     * @throws IOException If reading file fails.
     */
    public static InputStream readStream(File file) throws IOException {
        return new BufferedInputStream(new FileInputStream(file));
    }

    /**
     * Read character array from file. Take care with big files, this would be memory hogging,
     * rather use readReader() instead.
     * @param file The file to read the character array from.
     * @return The character array with the file contents.
     * @throws IOException If reading file fails.
     */
    public static char[] readChars(File file) throws IOException {
        BufferedReader reader = (BufferedReader) readReader(file);
        char[] chars = new char[(int) file.length()];
        reader.read(chars);
        return chars;
    }

    /**
     * Read string value from file. Take care with big files, this would be memory hogging, rather
     * use readReader() instead.
     * @param file The file to read the string value from.
     * @return The string value with the file contents.
     * @throws IOException If reading file fails.
     */
    public static String readString(File file) throws IOException {
        return new String(readChars(file));
    }

    /**
     * Read character reader from file.
     * @param file The file to read the character reader from.
     * @return The character reader with the file contents (actually: BufferedReader).
     * @throws IOException If reading file fails.
     */
    public static Reader readReader(File file) throws IOException {
        return new BufferedReader(new FileReader(file));
    }

    /**
     * Read list of String records from file.
     * @param file The file to read the character writer from.
     * @return A list of String records which represents lines of the file contents.
     * @throws IOException If reading file fails.
     */
    public static List<String> readRecords(File file) throws IOException {
        BufferedReader reader = (BufferedReader) readReader(file);
        List<String> records = new ArrayList<String>();
        String record = null;

        try {
            while ((record = reader.readLine()) != null) {
                records.add(record);
            }
        } finally {
            close(reader, file);
        }

        return records;
    }

    // Copiers ------------------------------------------------------------------------------------

    /**
     * Copy file. Any existing file at the destination will be overwritten.
     * @param source The file to read the contents from.
     * @param destination The file to write the contents to.
     * @throws IOException If copying file fails.
     */
    public static void copy(File source, File destination) throws IOException {
        copy(source, destination, true);
    }

    /**
     * Copy file with the option to overwrite any existing file at the destination.
     * @param source The file to read the contents from.
     * @param destination The file to write the contents to.
     * @param overwrite Set whether to overwrite any existing file at the destination.
     * @throws IOException If the destination file already exists while <tt>overwrite</tt> is set
     * to false, or if copying file fails.
     */
    public static void copy(File source, File destination, boolean overwrite) throws IOException {
        if (destination.exists() && !overwrite) {
            throw new IOException(
                "Copying file " + source.getPath() + " to " + destination.getPath() + " failed."
                    + " The destination file already exists.");
        }

        mkdirs(destination);
        BufferedInputStream input = null;
        BufferedOutputStream output = null;

        try {
            input = new BufferedInputStream(new FileInputStream(source));
            output = new BufferedOutputStream(new FileOutputStream(destination));
            int data = -1;
            while ((data = input.read()) != -1) {
                output.write(data);
            }
        } finally {
            close(input, source);
            close(output, destination);
        }
    }

    // Movers -------------------------------------------------------------------------------------

    /**
     * Move (rename) file. Any existing file at the destination will be overwritten.
     * @param source The file to be moved.
     * @param destination The new destination of the file.
     * @throws IOException If moving file fails.
     */
    public static void move(File source, File destination) throws IOException {
        move(source, destination, true);
    }

    /**
     * Move (rename) file with the option to overwrite any existing file at the destination.
     * @param source The file to be moved.
     * @param destination The new destination of the file.
     * @param overwrite Set whether to overwrite any existing file at the destination.
     * @throws IOException If the destination file already exists while <tt>overwrite</tt> is set
     * to false, or if moving file fails.
     */
    public static void move(File source, File destination, boolean overwrite) throws IOException {
        if (destination.exists()) {
            if (overwrite) {
                destination.delete();
            } else {
                throw new IOException(
                    "Moving file " + source.getPath() + " to " + destination.getPath() + " failed."
                        + " The destination file already exists.");
            }
        }

        mkdirs(destination);

        if (!source.renameTo(destination)) {
            throw new IOException(
                "Moving file " + source.getPath() + " to " + destination.getPath() + " failed.");
        }
    }

    // Filenames ----------------------------------------------------------------------------------

    /**
     * Trim the eventual file path from the given file name. Anything before the last occurred "/"
     * and "\" will be trimmed, including the slash.
     * @param fileName The file name to trim the file path from.
     * @return The file name with the file path trimmed.
     */
    public static String trimFilePath(String fileName) {
        return fileName
            .substring(fileName.lastIndexOf("/") + 1)
            .substring(fileName.lastIndexOf("\\") + 1);
    }

    /**
     * Generate unique file based on the given path and name. If the file exists, then it will
     * add "[i]" to the file name as long as the file exists. The value of i can be between
     * 0 and 2147483647 (the value of Integer.MAX_VALUE).
     * @param filePath The path of the unique file.
     * @param fileName The name of the unique file.
     * @return The unique file.
     * @throws IOException If unique file cannot be generated, this can be caused if all file
     * names are already in use. You may consider another filename instead.
     */
    public static File uniqueFile(File filePath, String fileName) throws IOException {
        File file = new File(filePath, fileName);
        
        if (file.exists()) {

            // Split filename and add braces, e.g. "name.ext" --> "name[", "].ext".
            String prefix;
            String suffix;
            int dotIndex = fileName.lastIndexOf(".");

            if (dotIndex > -1) {
                prefix = fileName.substring(0, dotIndex) + "[";
                suffix = "]" + fileName.substring(dotIndex);
            } else {
                prefix = fileName + "[";
                suffix = "]";
            }

            int count = 0;

            // Add counter to filename as long as file exists.
            while (file.exists()) {
                if (count < 0) { // int++ restarts at -2147483648 after 2147483647.
                    throw new IOException("No unique filename available for " + fileName 
                        + " in path " + filePath.getPath() + ".");
                }

                // Glue counter between prefix and suffix, e.g. "name[" + count + "].ext".
                file = new File(filePath, prefix + (count++) + suffix);
            }
        }

        return file;
    }

    // Helpers ------------------------------------------------------------------------------------

    /**
     * Check and create missing parent directories for the given file.
     * @param file The file to check and create the missing parent directories for.
     * @throws IOException If the given file is actually not a file or if creating parent 
     * directories fails.
     */
    private static void mkdirs(File file) throws IOException {
        if (file.exists() && !file.isFile()) {
            throw new IOException("File " + file.getPath() + " is actually not a file.");
        }
        File parentFile = file.getParentFile();
        if (!parentFile.exists() && !parentFile.mkdirs()) {
            throw new IOException("Creating directories " + parentFile.getPath() + " failed.");
        }
    }

    /**
     * Close the given I/O resource of the given file.
     * @param resource The I/O resource to be closed.
     * @param file The I/O resource's subject.
     */
    private static void close(Closeable resource, File file) {
        if (resource != null) {
            try {
                resource.close();
            } catch (IOException e) {
                String message = "Closing file " + file.getPath() + " failed.";
                // Do your thing with the exception and the message. Print it, log it or mail it.
                System.err.println(message);
                e.printStackTrace();
            }
        }
    }
    
}

Copyright - GNU Lesser General Public License

(C) September 2007, BalusC

Monday, August 6, 2007

Generic object converter

Generic Object Converter

This generic ObjectConverter makes use of the reflection and the generics to convert the given object value to an object of the given type. This utility class may look like overkilled, but it is very useful if you don't always know the type of the given object value before or if you want a fast and easy way of converting objects without nasty if/else blocks. BalusC uses it in his private/hobby/homegrown ORM to convert unmatched types from the ResultSet#getObject() to Entity#setSomething() and also in his private/hobby/homegrown MVC framework to convert String request parameters to the desired type.

/*
 * net/balusc/util/ObjectConverter.java
 * 
 * Copyright (C) 2007 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.util;

import java.lang.reflect.Method;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;

/**
 * Generic object converter.
 * <p>
 * <h3>Use examples</h3>
 * 
 * <pre>
 * Object o1 = Boolean.TRUE;
 * Integer i = ObjectConverter.convert(o1, Integer.class);
 * System.out.println(i); // 1
 * 
 * Object o2 = "false";
 * Boolean b = ObjectConverter.convert(o2, Boolean.class);
 * System.out.println(b); // false
 * 
 * Object o3 = new Integer(123);
 * String s = ObjectConverter.convert(o3, String.class);
 * System.out.println(s); // 123
 * </pre>
 * 
 * Not all possible conversions are implemented. You can extend the <tt>ObjectConverter</tt>
 * easily by just adding a new method to it, with the appropriate logic. For example:
 * 
 * <pre>
 * public static ToObject fromObjectToObject(FromObject fromObject) {
 *     // Implement.
 * }
 * </pre>
 * 
 * The method name doesn't matter. It's all about the parameter type and the return type.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2007/08/generic-object-converter.html
 */
public final class ObjectConverter {

    // Init ---------------------------------------------------------------------------------------

    private static final Map<String, Method> CONVERTERS = new HashMap<String, Method>();

    static {
        // Preload converters.
        Method[] methods = ObjectConverter.class.getDeclaredMethods();
        for (Method method : methods) {
            if (method.getParameterTypes().length == 1) {
                // Converter should accept 1 argument. This skips the convert() method.
                CONVERTERS.put(method.getParameterTypes()[0].getName() + "_"
                    + method.getReturnType().getName(), method);
            }
        }
    }

    private ObjectConverter() {
        // Utility class, hide the constructor.
    }

    // Action -------------------------------------------------------------------------------------

    /**
     * Convert the given object value to the given class.
     * @param from The object value to be converted.
     * @param to The type class which the given object should be converted to.
     * @return The converted object value.
     * @throws NullPointerException If 'to' is null.
     * @throws UnsupportedOperationException If no suitable converter can be found.
     * @throws RuntimeException If conversion failed somehow. This can be caused by at least
     * an ExceptionInInitializerError, IllegalAccessException or InvocationTargetException.
     */
    public static <T> T convert(Object from, Class<T> to) {

        // Null is just null.
        if (from == null) {
            return null;
        }

        // Can we cast? Then just do it.
        if (to.isAssignableFrom(from.getClass())) {
            return to.cast(from);
        }

        // Lookup the suitable converter.
        String converterId = from.getClass().getName() + "_" + to.getName();
        Method converter = CONVERTERS.get(converterId);
        if (converter == null) {
            throw new UnsupportedOperationException("Cannot convert from " 
                + from.getClass().getName() + " to " + to.getName()
                + ". Requested converter does not exist.");
        }

        // Convert the value.
        try {
            return to.cast(converter.invoke(to, from));
        } catch (Exception e) {
            throw new RuntimeException("Cannot convert from " 
                + from.getClass().getName() + " to " + to.getName()
                + ". Conversion failed with " + e.getMessage(), e);
        }
    }

    // Converters ---------------------------------------------------------------------------------

    /**
     * Converts Integer to Boolean. If integer value is 0, then return FALSE, else return TRUE.
     * @param value The Integer to be converted.
     * @return The converted Boolean value.
     */
    public static Boolean integerToBoolean(Integer value) {
        return value.intValue() == 0 ? Boolean.FALSE : Boolean.TRUE;
    }

    /**
     * Converts Boolean to Integer. If boolean value is TRUE, then return 1, else return 0.
     * @param value The Boolean to be converted.
     * @return The converted Integer value.
     */
    public static Integer booleanToInteger(Boolean value) {
        return value.booleanValue() ? Integer.valueOf(1) : Integer.valueOf(0);
    }

    /**
     * Converts Double to BigDecimal.
     * @param value The Double to be converted.
     * @return The converted BigDecimal value.
     */
    public static BigDecimal doubleToBigDecimal(Double value) {
        return new BigDecimal(value.doubleValue());
    }

    /**
     * Converts BigDecimal to Double.
     * @param value The BigDecimal to be converted.
     * @return The converted Double value.
     */
    public static Double bigDecimalToDouble(BigDecimal value) {
        return new Double(value.doubleValue());
    }

    /**
     * Converts Integer to String.
     * @param value The Integer to be converted.
     * @return The converted String value.
     */
    public static String integerToString(Integer value) {
        return value.toString();
    }

    /**
     * Converts String to Integer.
     * @param value The String to be converted.
     * @return The converted Integer value.
     */
    public static Integer stringToInteger(String value) {
        return Integer.valueOf(value);
    }

    /**
     * Converts Boolean to String.
     * @param value The Boolean to be converted.
     * @return The converted String value.
     */
    public static String booleanToString(Boolean value) {
        return value.toString();
    }

    /**
     * Converts String to Boolean.
     * @param value The String to be converted.
     * @return The converted Boolean value.
     */
    public static Boolean stringToBoolean(String value) {
        return Boolean.valueOf(value);
    }

    // You can implement more converter methods here.

}

This works by the way also with primitives.

Copyright - GNU Lesser General Public License

(C) August 2007, BalusC

Tuesday, June 13, 2006

Parse CSV upload

How easy can it be done?

CSV files are commaseparated files and can be opened and saved using Microsoft Excel. However, in most locales (at least US International and Europe) Excel uses a semicolon instead of a comma to separate fields. With the correct field separator the CSV files will be opened automatically in the right rows and columns in Excel. And saving an Excel table as CSV without using any wizard will deliver CSV files with the locale default field separation.

According to the RFC 4180 spec there are 3 important rules to keep in mind (you may also read "comma" as "semicolon"):

  1. Fields are separated by a comma.
  2. If a comma occurs within a field, then the field have to be surrounded by double quotes.
  3. If a double quote occurs within a field, then the field have to be surrounded by double quotes and the double quote within the field have to be escaped by another double quote.

Valid CSV records are:

field1,field2,field3
"field1,",field2,"fie""ld3"
"""field1""",",field2,",","","","""

Which should be parsed as:

field1    field2    field3
field1,   field2    fie"ld3
"field1"  ,field2,  ,",","

Back to top

Uploading a CSV file using IBM faces

Here is a basic example of the JSF code:

<hx:scriptCollector id="scriptCollector1">
    ...
    <h:form id="csvupload">
        <hx:fileupload 
            id="csvfile"
            binding="#{myBean.csvFile}"
            accept="application/vnd.ms-excel"
        />
        <h:message for="csvfile" />
        <h:commandButton
            value="upload"
            action="#{myBean.uploadCsvFile}"
        />
    </h:form>
    ...
</hx:scriptCollector>

The h:message is a placeholder for any errormessage from the hx:fileupload if the application type of the uploaded file don't match the MIME type application/vnd.ms-excel. If you don't need the accept attribute of the hx:fileupload, then just leave this attribute and the h:message away.

The relevant java code for the backing bean MyBean.java:

package mypackage;

import java.io.ByteArrayInputStream;
import java.util.List;

import com.ibm.faces.component.html.HtmlFileupload;
import com.ibm.faces.fileupload.util.ContentElement;

import net.balusc.util.CsvUtil;

public class MyBean {

    // Init --------------------------------------------------------------------------------------

    private HtmlFileUpload csvFile;

    // Actions -----------------------------------------------------------------------------------

    public void uploadCsvFile() {

        // Get uploaded csv file.
        ContentElement csvContent = (ContentElement) csvFile.getValue();

        // Process uploaded csv file.
        if (csvContent != null) {
            ByteArrayInputStream csvInput = new ByteArrayInputStream(csvContent.getContentValue());
            List<List<String>> csvList = CsvUtil.parseCsv(csvInput, ';');
            // Now you can do your thing with the CSV List.
        } else {
            // Empty file error, do your thing.
        }
    }

    // Getters -----------------------------------------------------------------------------------

    public HtmlFileupload getCsvFile() {
        return csvFile;
    }
    
    // Setters -----------------------------------------------------------------------------------

    public void setCsvFile(HtmlFileupload csvFile) {
        this.csvFile = csvFile;
    }

}

The useful CsvUtil class is described in the next chapter:

Back to top

Parsing and formatting the CSV file

This kind of a CSV parser accepts an InputStream which can be feeded by a file upload, reading a local file or any other kind of a binary stream. And it returns a two-dimensional List. The first List contains the CSV records in the appropriate order and the second List contains the fields of each CSV record in the appropriate order.

Formatting a CSV is also relatively easy: just turn the parser around. The CSV formatter should accept a two-dimensional List and return an InputStream with which you can do anything. Saving to a file or streaming it to the response as a file download.

/*
 * net/balusc/util/CsvUtil.java
 * 
 * Copyright (C) 2006 BalusC
 * 
 * This program is free software: you can redistribute it and/or modify it under the terms of the
 * GNU Lesser General Public License as published by the Free Software Foundation, either version 3
 * of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
 * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along with this library.
 * If not, see <http://www.gnu.org/licenses/>.
 */

package net.balusc.util;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

/**
 * Useful CSV utilities.
 * 
 * @author BalusC
 * @link http://balusc.blogspot.com/2006/06/parse-csv-upload.html
 */
public class CsvUtil {

    // Init ---------------------------------------------------------------------------------------

    // Defaults.
    private static final char DEFAULT_CSV_SEPARATOR = ',';
    private static final String DEFAULT_LINE_SEPARATOR = "\r\n"; // CRLF.

    private CsvUtil() {
        // Utility class, hide the constructor.
    }

    // Parsers ------------------------------------------------------------------------------------

    /**
     * CSV content parser. Convert an InputStream with the CSV contents to a two-dimensional List
     * of Strings representing the rows and columns of the CSV. Each CSV record is expected to be
     * separated by the default CSV field separator, a comma.
     * @param csvInput The InputStream with the CSV contents.
     * @return A two-dimensional List of Strings representing the rows and columns of the CSV.
     */
    public static List<List<String>> parseCsv(InputStream csvInput) {
        return parseCsv(csvInput, DEFAULT_CSV_SEPARATOR);
    }

    /**
     * CSV content parser. Convert an InputStream with the CSV contents to a two-dimensional List
     * of Strings representing the rows and columns of the CSV. Each CSV record is expected to be
     * separated by the specified CSV field separator.
     * @param csvInput The InputStream with the CSV contents.
     * @param csvSeparator The CSV field separator to be used.
     * @return A two-dimensional List of Strings representing the rows and columns of the CSV.
     */
    public static List<List<String>> parseCsv(InputStream csvInput, char csvSeparator) {

        // Prepare.
        BufferedReader csvReader = null;
        List<List<String>> csvList = new ArrayList<List<String>>();
        String csvRecord = null;

        // Process records.
        try {
            csvReader = new BufferedReader(new InputStreamReader(csvInput, "UTF-8"));
            while ((csvRecord = csvReader.readLine()) != null) {
                csvList.add(parseCsvRecord(csvRecord, csvSeparator));
            }
        } catch (IOException e) {
            throw new RuntimeException("Reading CSV failed.", e);
        } finally {
            if (csvReader != null) try {
                csvReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        return csvList;
    }

    /**
     * CSV record parser. Convert a CSV record to a List of Strings representing the fields of the
     * CSV record. The CSV record is expected to be separated by the specified CSV field separator.
     * @param record The CSV record.
     * @param csvSeparator The CSV field separator to be used.
     * @return A List of Strings representing the fields of each CSV record.
     */
    private static List<String> parseCsvRecord(String record, char csvSeparator) {

        // Prepare.
        boolean quoted = false;
        StringBuilder fieldBuilder = new StringBuilder();
        List<String> fields = new ArrayList<String>();

        // Process fields.
        for (int i = 0; i < record.length(); i++) {
            char c = record.charAt(i);
            fieldBuilder.append(c);

            if (c == '"') {
                quoted = !quoted; // Detect nested quotes.
            }

            if ((!quoted && c == csvSeparator) // The separator ..
                || i + 1 == record.length()) // .. or, the end of record.
            {
                String field = fieldBuilder.toString() // Obtain the field, ..
                    .replaceAll(csvSeparator + "$", "") // .. trim ending separator, ..
                    .replaceAll("^\"|\"$", "") // .. trim surrounding quotes, ..
                    .replace("\"\"", "\""); // .. and un-escape quotes.
                fields.add(field.trim()); // Add field to List.
                fieldBuilder = new StringBuilder(); // Reset.
            }
        }

        return fields;
    }

    // Formatters --------------------------------------------------------------------------------

    /**
     * CSV content formatter. Convert a two-dimensional List of Objects to a CSV in an InputStream.
     * The value of each Object will be obtained by its toString() method. The fields of each CSV 
     * record will be separated by the default CSV field separator, a comma.
     * @param csvList A two-dimensional List of Objects representing the rows and columns of the
     * CSV.
     * @return The InputStream containing the CSV contents (actually a ByteArrayInputStream).
     */
    public static <T extends Object> InputStream formatCsv(List<List<T>> csvList) {
        return formatCsv(csvList, DEFAULT_CSV_SEPARATOR);
    }

    /**
     * CSV content formatter. Convert a two-dimensional List of Objects to a CSV in an InputStream.
     * The value of each Object will be obtained by its toString() method. The fields of each CSV
     * record will be separated by the specified CSV field separator.
     * @param csvList A two-dimensional List of Objects representing the rows and columns of the
     * CSV.
     * @param csvSeparator The CSV field separator to be used.
     * @return The InputStream containing the CSV contents (actually a ByteArrayInputStream).
     */
    public static <T extends Object> InputStream formatCsv(List<List<T>> csvList, char csvSeparator) {

        // Prepare.
        StringBuilder csvContent = new StringBuilder();

        // Process records.
        for (List<T> csvRecord : csvList) {
            if (csvRecord != null) {
                csvContent.append(formatCsvRecord(csvRecord, csvSeparator));
            }

            // Add default line separator.
            csvContent.append(DEFAULT_LINE_SEPARATOR);
        }

        return new ByteArrayInputStream(csvContent.toString().getBytes());
    }

    /**
     * CSV record formatter. Convert a List of Objects representing the fields of a CSV record to a
     * String representing the CSV record. The value of each Object will be obtained by its
     * toString() method. The fields of the CSV record will be separated by the specified CSV field
     * separator.
     * @param csvRecord A List of Objects representing the fields of a CSV reecord.
     * @param csvSeparator The CSV field separator to be used.
     * @return A String representing a CSV record.
     */
    private static <T extends Object> String formatCsvRecord(List<T> csvRecord, char csvSeparator) {

        // Prepare.
        StringBuilder fields = new StringBuilder();
        String separator = String.valueOf(csvSeparator);

        // Process fields.
        for (Iterator<T> iter = csvRecord.iterator(); iter.hasNext();) {
            T object = iter.next();

            if (object != null) {
                String field = object.toString().replace("\"", "\"\""); // Escape quotes.

                if (field.contains(separator) || field.contains("\"")) {
                    field = "\"" + field + "\""; // Surround with quotes.
                }

                fields.append(field);
            }

            if (iter.hasNext()) {
                fields.append(separator); // Add field separator.
            }
        }

        return fields.toString();
    }

}
Back to top

CSV formatting example

Here is an example how to create, format and save a CSV file quickly:

package mypackage;

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

import net.balusc.util.CsvUtil;
import net.balusc.util.FileUtil;

public class Test {

    public static void main(String[] args) throws IOException {

        // Create CSV.
        List<List<String>> csvList = new ArrayList<List<String>>();
        csvList.add(Arrays.asList("field1", "field2", "field3"));
        csvList.add(Arrays.asList("field1,", "field2", "fie\"ld3"));
        csvList.add(Arrays.asList("\"field1\"", ",field2,", ",\",\",\""));

        // Format CSV.
        InputStream csvInput = CsvUtil.formatCsv(csvList, ';');

        // Save CSV.
        FileUtil.write(new File("c:/test.csv"), csvInput);
    }

}

You can find the FileUtil here by the way.

Back to top

Copyright - GNU Lesser General Public License

(C) June 2006, BalusC