Показаны сообщения с ярлыком java core. Показать все сообщения
Показаны сообщения с ярлыком java core. Показать все сообщения

пятница, 11 ноября 2011 г.

Чтение и запись Tab Separated Values файлов

Что же представляют из себя файлы в формате TSV? Это текстовые файлы, представляющие собой набор строк. В каждой строке одинаковое количество "полей" определенного формата, разделенные символом табуляции ("\t"). Похоже на некое подобие таблицы.

Итак, приступим к написанию reader-а:
/**
 * @author vie
 *
 * TSVReader.java
 * Class for read tab separated values files
 */
public class TSVReader<T> {

    private final String filePath;

    public TSVReader(String filePath) {
        this.filePath = filePath;
    }

    /**
     * This method reads tab separated values file
     * into object of the arbitrary class
     *
     * @param clazz type of transmitted class
     * @return List
     * @throws com.site.tsv.exceptions.TSVException
     */
    public List<T> read(Class<T> clazz) throws TSVException {
        List<T> result = new LinkedList<T>();

        try {
            BufferedReader bReader = new BufferedReader(new FileReader(filePath));
            String line;

            //recieving all lines
            int i = 1;
            while ((line = bReader.readLine()) != null) {
                //recieving all line members
                String datavalue[] = line.split("\t");

                //create instance of line entity
                Constructor constructor = clazz.getConstructor(null);
                Object somethingInstance = constructor.newInstance(null);

                //parsing line entity members
                Field[] fields = clazz.getDeclaredFields();

                //recieve map of line entity member types
                Map<Integer, Class<?>> mapFields = defineFields(clazz);

                //for each line entity field
                if (fields != null && datavalue != null && fields.length == datavalue.length) {
                    for (int j = 0; j < fields.length; j++) {
                        //for each line entity field
                        String s = fields[j].getName();
                        s = s.substring(0, 1).toUpperCase() + s.substring(1, s.length());

                        //exec line entity setter methods
                        Method method = clazz.getMethod("set" + s, mapFields.get(j));
                        method.invoke(somethingInstance, getDeterminatedValue(datavalue[j], fields[j].getType()));
                    }
                } else if (fields != null && datavalue != null && fields.length != datavalue.length) {
                    throw new TSVInvalidFileException("Count of formatted file fields on line " + i + " not equal count of entity fields");
                }

                i++;
                result.add((T) somethingInstance);
            }
            bReader.close();
        } catch (FileNotFoundException e) {
            throw new TSVException(e);
        } catch (IOException e) {
            throw new TSVException(e);
        } catch (ParseException e) {
            throw new TSVInvalidFileException(e);
        } catch (NumberFormatException e) {
            throw new TSVInvalidFileException(e);
        } catch (NoSuchMethodException e) { //reflect
            throw new TSVException(e);
        } catch (InstantiationException e) {
            throw new TSVException(e);
        } catch (IllegalAccessException e) {
            throw new TSVException(e);
        } catch (InvocationTargetException e) {
            throw new TSVException(e);
        }
        return result;
    }

    /**
     * Returns field names and types
     *
     * @param clazz type of transmitted class
     * @return Map<string,integer>.
     * @throws com.site.tsv.exceptions.TSVIncorrectAnnotatedFieldsException
     */
    private Map<Integer, Class<?>> defineFields(Class<?> clazz) throws TSVIncorrectAnnotatedFieldsException {
        Map<Integer, Class<?>> map = new HashMap<Integer, Class<?>>();

        Field[] fields = clazz.getDeclaredFields();
        int i = 0;
        for (Field field : fields) {
            if (field.isAnnotationPresent(TSVInteger.class)) {
                map.put(i++, Integer.class);
            } else if (field.isAnnotationPresent(TSVDouble.class)) {
                map.put(i++, Double.class);
            } else if (field.isAnnotationPresent(TSVString.class)) {
                map.put(i++, String.class);
            } else if (field.isAnnotationPresent(TSVDate.class)) {
                map.put(i++, Date.class);
            }
        }

        if (fields.length == 0 || fields.length != map.size()) {
            throw new TSVIncorrectAnnotatedFieldsException("Some field not annotated or count of fields = 0!");
        }

        return map;
    }

    /**
     * Returns converted to the native type field
     *
     * @param original value of obtained field
     * @param needType type of transmitted class
     * @return Object
     * @throws java.text.ParseException
     */
    private Object getDeterminatedValue(String original, Class<?> needType) throws ParseException {
        if (needType.getName().equals("java.lang.Integer")) {
            return Integer.parseInt(original);
        } else if (needType.getName().equals("java.lang.Double")) {
            return Double.parseDouble(original);
        } else if (needType.getName().equals("java.lang.String")) {
            return original;
        } else if (needType.getName().equals("java.util.Date")) {
            SimpleDateFormat formatter = new SimpleDateFormat("dd.MM.yyyy");
            return formatter.parse(original);
        } else {
            return null;
        }
    }
}
В классе я использовал generics, чтобы проверять заданного типы entity на этапе компиляции. В преобразовании строк с различными типами данных в entity объект мне помог reflection. Для разбора с его помощью entity, написаны приведенные ниже аннотации. Также я создал несколько исключений, чтобы в при выпадении ошибок можно было быстро понять что свалилось, будь то reflection, IO или преобразование чисел и дат.

среда, 9 ноября 2011 г.

Разделение выполнения задачи на n потоков

Бывает при решении некоторых задач, таких как загрузка данных с FTP приходится разделять ход выполнения задачи на несколько потоков.
Казалось бы, все просто. Создаем n-е количество потоков (каждый из которых делает часть общей работы) и запускаем их:
public class MainClass {
  public static void main(String[] args) {
    WorkClass work = new WorkClass();
    work.doSomeWork();
  }
}

public class WorkClass {

  public void doSomeWork() {             
    for (int i = 0; i < 5; i++) { 
      Worker worker = new Worker("directory" + i);
      Thread thread = new Thread(worker);
      thread.start();
    }
    
    //here we must wait for execution of all threads

    //doing work on    
  }

  public class Worker implements Runnable {

    private String dir;

    public Worker(String dir) {
      this.dir = dir;
    }
  
    @Override
    public void run() {
      doPieceOfWork();
    }

    private void doPieceOfWork() {
      //doing loading from FTP, depending on the name of directory ...
    }
  }
}
Здесь в качестве примера разделения выполняемой работы на 5 потоков приводится загрузка по FTP. Каждый поток грузит свою папку. Но может возникнуть проблема. Надо подождать, пока выполнятся все запущенные потоки, и только после этого продолжать работу основного потока. Для этого обернем вызов потоков в блок синхронизации.
public void doSomeWork() {
  synchronized (this) {             
    for (int i = 0; i < 5; i++) { 
      Worker worker = new Worker("directory" + i);
      Thread thread = new Thread(worker);
      thread.start();
    }
  }
}
Далее остановим главный поток после запуска второстепенных потоков до тех пор, пока не получим ответ о завершении работы каждого из них. Для этого сделаем цикл, в котором получаем уведомление от одного из потоков и, если количество завершенных второстепенных потоков меньше количества запущенных, продолжаем ожидать ответа от оставшихся:
public void doSomeWork() {
  synchronized (this) { 
    Vector events = new Vector();
            
    for (int i = 0; i < 5; i++) {       
      Worker worker = new Worker(this, "directory" + i, events);
      Thread thread = new Thread(worker);
      thread.start();
    }

    //if all threads are dead, go ahead
    while (true) {
      try {
        this.wait();
      } catch (InterruptedException e) {
        errorLogger.error(e.getMessage(), e);
      }
      if (events.size() == 5) {
        break;
      }
    }        
  }

  //doing work on
}
теперь надо добавить во второстепенные потоки уведомления, которые будет получать основной поток:
public class Worker implements Runnable {

  private String dir;

  private Vector events;

  public Worker(WorkClass monitor, String dir, Vector events) {
    this.dir = dir;
    this.events = events;
  }
  
  @Override
  public void run() {
    doPieceOfWork();
  }

  private void doPieceOfWork() {
    //doing loading from FTP, depending on the name of directory ...
    
    synchronized (monitor) {
      events.add(new Object());
      monitor.notify();      
    }
  }
}
Cледует обратить внимание, что блоки синхронизации во второстепенных потоках и в главном создаются по монитору work класса WorkClass. Также в класс Worker добавлена коллекция events, которая выполняет роль счетчика завершенных потоков. Класс Vector выбран не случайно, тк его методы синхронизированы. В качестве его альтернативы можно воспользоваться Collections.synchronizedList(new ArrayList()). Хотя в данном случае синхронизация методов коллекции и не будет играть никакой роли, тк работа с ней происходит в блоке синхронизации. Все, теперь метод doSomeWork() разделит загрузку по FTP на 5 потоков и продолжит свою работу после завершения всех запущенных потоков. Общий результат выглядит так:
public class MainClass {
  public static void main(String[] args) {
    WorkClass work = new WorkClass();
    work.doSomeWork();
  }
}

public class WorkClass {
  protected static final Logger errorLogger = LoggerFactory.getLogger("errorLogger");

  public void doSomeWork() {
    synchronized (this) {     
      Vector events = new Vector();
        
      for (int i = 0; i < 5; i++) { 
        Worker worker = new Worker(this, "directory" + i, events);
        Thread thread = new Thread(worker);
        thread.start();
      }
      
      //if all threads are dead, go ahead
      while (true) {
        try {
          this.wait();
        } catch (InterruptedException e) {
          errorLogger.error(e.getMessage(), e);
        }
        if (events.size() == 5) {
          break;
        }
      }
    }
    
    //doing work on 
  }

  public class Worker implements Runnable {

    private String dir;

    private Vector events;

    public Worker(WorkClass monitor, String dir, Vector events) {
      this.dir = dir;
      this.events = events;
    }
  
    @Override
    public void run() {
      doPieceOfWork();
    }

    private void doPieceOfWork() {
      //doing loading from FTP, depending on the name of directory ...
    
      synchronized (monitor) {
        events.add(new Object());
        monitor.notify();        
      }
    }
  }
}

четверг, 9 июня 2011 г.

Double checked locking

Шаблон проектирования "Блокировка с двойной проверкой" используется в многопоточном программировании. Это один из излюбленных вопросов на собеседованиях. Звучит он обычно как просьба написать синглетон, корректно работающий в многопоточном режиме.

Рассмотрим принцип паттерна на примере Синглетона.
// Не работает в Java 1.4 и более ранних версиях из-за семантики volatile
class Foo {
  private volatile Helper helper = null;  

  public Helper getHelper() {
    if (helper == null) {          //блок проверки, для ускорения производительности
      synchronized(this) {         //блок синхронизации
        if (helper == null)        //если обьект еще не создан, то создаем его
          helper = new Helper();
      }
    }
    return helper;
  }
}

Первый блок проверки нужен для того, чтобы при инициализированной переменной не блокировать участок кода, тем самым ускорить процесс выполнения программы. Внутренняя проверка служит для проверки, инициализирована ли переменная, чтобы в последуюущих случаях обращения к ней после инициализации выдавался уже созданный ее экземпляр. Блок синхронизации разделяет доступ потоков к коду, инициализирующему переменную.

Модификатор volatile появился начиная с версии Java 1.5. Он позволяет корректно обработать запись в переменную в многопоточном режиме.

среда, 11 мая 2011 г.

Typesafe Enum pattern или как обойтись без Enum-ов.

Часто нужда в использовании Enum возникает в случае, если необходимо передать в метод одно из значений, например "green", "red" или "blue". Первое что приходит в голову - это принимать String:
public void method(String param) {...}

Но в таком случае в качестве параметра может быть принята любая строка. В соответствии с принципом, что все потенциальные ошибки по возможности надо переносить на момент компиляции, такой вариант неприемлем. Тут и приходит на помощь Enum:

public enum TypesafeEnum {GREEN, RED, BLUE};

А можно ли обойтись без него? Конечно можно, ведь Enum появились только с java 1.5. Тут нам может помочь паттерн Typesafe Enum:
public class TypesafeEnum {

  private final String name;

  private TypesafeEnum(String name) {
    this.name = name;
  }

  public static final TypesafeEnum GREEN = new TypesafeEnum("green");
  public static final TypesafeEnum RED = new TypesafeEnum("red");
  public static final TypesafeEnum BLUE = new TypesafeEnum("blue");

  public String toString() {
    return name;
  }
}
Теперь можно обьявить наш метод таким образом:
public void method(TypesafeEnum param) {...}
соответственно его вызов:
someObject.method(TypesafeEnum.RED);


Подробнее о паттерне здесь

суббота, 9 апреля 2011 г.

Simple ORM

Наверное многие начинающие java программисты задумывались над тем, как неудобно писать слой взаимодейстия с базой данных на чистом JDBC. Ctrl+c, Ctrl+v и изменение названия таблиц и параметров. Все это приводит к излишнему дублированию кода. Конечно можно сразу взять тот же Hibernate или любую другую реализацию JPA. Но для небольших и простых проектов, имхо это как "из пушки по воробьям". К тому же считаю что надо один раз написать ORM самому, чтобы понять основы его работы.

В этой статье рассмотрим создание своего простого ORM на примере работы с СУБД Oracle.

Создадим интерфейс Persistence и реализующий его класс PersistenceOraImpl - собственно сам персистенс Api для работы с БД.

Список методов в Persistence:

1. selectAll - выбирает все записи из таблицы;
2. select - выбирает записи по заданным параметрам;
3. selectRow - выбирает одну запись по заданным параметрам;
4. insert - вставляет запись;
5. update - обновляет запись;
6. delete - удаляет запись.

Сам интерфейс выглядит так:
public interface Persistence {

 public List selectAll(Class<?> clazz);

 public List select(Class<?> clazz, Map<String, Object> params);

 public Object selectRow(Class<?> clazz, Map<String, Object> params);

 public void insert(Object obj);

 public void update(Object obj);

 public void delete(Class<?> clazz, Map<String, Object> params);

 public Connection getConnection();

 public void setConnection(Connection conn);
}

вторник, 5 апреля 2011 г.

Сериализация обьекта

отправка обьекта:
Message message = new Message();
 ByteArrayOutputStream fos = new ByteArrayOutputStream();
 ObjectOutputStream oos = new ObjectOutputStream(fos);
 oos.writeObject(message);
 oos.flush();
 oos.close();

 byte[] messageBodyBytes = fos.toByteArray();
где Message - POJO обьект.

Далее обьект можно записать в файл или отправить в сообщении.

прием обьекта:
ByteArrayInputStream fis = new ByteArrayInputStream(messageBodyBytes);
 ObjectInputStream oin = new ObjectInputStream(fis);
 Message mess = (Message) oin.readObject();