웹에서 Javascript 만으로 텍스트 파일 읽기

웹에서 JS 언어만으로 로컬에 저장된 텍스트 파일을 읽어 오는 코드를 정리한 글이다. 먼저 아래는 예제 코드 실행을 위한 DOM 구성에 대한 코드이다.


...

Open 버튼을 클릭하면 텍스트 파일을 선택할 수 있는 대화상자가 표시되도록 하며, 여기서 읽고자 하는 파일을 사용자가 선택하면 id가 output인 div에 텍스트 파일의 내용을 출력한다. 이에 대한 코드는 아래와 같다.

function openTextFile() {
    var input = document.createElement("input");

    input.type = "file";
    input.accept = "text/plain"; // 확장자가 xxx, yyy 일때, ".xxx, .yyy"

    input.onchange = function (event) {
        processFile(event.target.files[0]);
    };

    input.click();
}

function processFile(file) {
    var reader = new FileReader();

    reader.onload = function () {
        output.innerText = reader.result;
    };

    reader.readAsText(file, /* optional */ "euc-kr");
}

openTextFile은 Open 버튼 클릭시 호출하는 함수이다. processFile은 openTextFile에서 호출되는 함수로 선택된 파일을 읽어 div에 그 내용을 출력한다. IE와 Chrome 모두에서 정상적으로 작동하는 것을 확인했다.

웹에서 Javascript 만으로 텍스트 파일 생성

웹에서 스크립트만으로 텍스트 파일을 생성하기 위한 코드를 정리한다. 주로 사용하는 웹브라우져가 IE와 Chrome인데, 텍스트 파일을 생성하는 방식이 서로 다르다. 먼저 크롬의 경우에는 아래와 같다.

function saveToFile_Chrome(fileName, content) {
    var blob = new Blob([content], { type: 'text/plain' });

    objURL = window.URL.createObjectURL(blob);
            
    // 이전에 생성된 메모리 해제
    if (window.__Xr_objURL_forCreatingFile__) {
        window.URL.revokeObjectURL(window.__Xr_objURL_forCreatingFile__);
    }
    window.__Xr_objURL_forCreatingFile__ = objURL;

    var a = document.createElement('a');

    a.download = fileName;
    a.href = objURL;
    a.click();
}

다음은 IE에서 작동하는 코드이다.

function saveToFile_IE(fileName, content) {
    var blob = new Blob([content], { type: "text/plain", endings: "native" });

    window.navigator.msSaveBlob(blob, fileName);
    //window.navigator.msSaveOrOpenBlob(blob, fileName);
}

아래는 현재 사용하는 웹브라우저가 IE인지를 식별하는 함수이다.

function isIE() {
    return (navigator.appName === 'Netscape' && navigator.userAgent.search('Trident') !== -1) ||
        navigator.userAgent.toLowerCase().indexOf("msie") !== -1;
}

[Java] 원하는 시간에 원하는 기능을 실행해 주는 Scheduler 라이브러리, Quartz v2.2.3

원하는 시간에 원하는 기능을 실행해 주는 스케줄러 기능을 Java에서 실행하기 위해서 가장 많이 사용되는 라이브러리가 Quartz입니다. Quartz는 기능을 안정적으로 실행할 수 있다는 장점과 함께, 특히 실행할 시간을 매우 유연하게 지정할 수 있습니다.

아래의 코드는 매 순간의 시간이 5초일때(5초마다가 아님) 특정한 기능, 즉 Job을 실행해 주는 코드입니다.

package quartz;

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;

public class MainEntry {
    public static void main(String[] args) {
        SchedulerFactory schedulerFactory = new StdSchedulerFactory();
		
        try {
            Scheduler scheduler = schedulerFactory.getScheduler();
			
            JobDetail job = newJob(TestJob.class)
                .withIdentity("jobName", Scheduler.DEFAULT_GROUP)
                .build();
			
            Trigger trigger = newTrigger()
                .withIdentity("trggerName", Scheduler.DEFAULT_GROUP)
                .withSchedule(cronSchedule("5 * * * * ?"))
                .build();
						
            scheduler.scheduleJob(job, trigger);
            scheduler.start();
        } catch(Exception e) {
            e.printStackTrace();
        }		
    }
}

26번째 코드를 보면 cronSchedule 함수를 통해 Job을 실행할 시간을 정하고 있습니다. 시간을 정하는 방식을 5 * * * * ?인데요. 순서대로 초 분 시 일 월 요일 년도(옵션)입니다. 즉 초를 5로 하고 나머지에 대해서는 * 또는 ?로 지정함으로써 매순간의 시간이 5초일때 Job을 실행하게 됩니다. 그럼, 실행할 Job은 어떻게 지정할까요? 20번 코드의 newJob 함수를 통해 지정하고 있습니다. Job으로 TestJob을 사용하고 있는데.. 이 TestJob의 코드는 아래와 같습니다.

package quartz;

import java.util.Date;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class TestJob implements Job {
    @Override
    public void execute(JobExecutionContext arg0) throws JobExecutionException {
        System.out.println("Job Executed [" + new Date(System.currentTimeMillis()) + "]"); 
    }
}

위의 예에서는 간단히 Job이 실행되는 시간과 Job Executed라는 문자열을 표시하는 일만 합니다.

Quartz 라이브러리는 http://www.quartz-scheduler.org/downloads 에서 받을 수 있으며, 다운로드 받은 jar 중 최소한 quartz-{version}.jar와 slf4j-api-{version}.jar를 참조해줘야 합니다.

회전 제한 및 양방향 성질을 가진 네트워크 DB를 활용한 A* 알고리즘

실제 도로망은 U턴 제한이나 우회전 제한 등과 같은 회전 제한에 대한 성질과 좌측 및 우측 차로에 대한 방향에 대한 성질을 가집니다. 이러한 성질에 대한 속성값을 가지는 네트워크 데이터는 지능형 교통체계 표준 노드링크 관리시스템(http://nodelink.its.go.kr)에서 제공하고 있습니다.

이 글은 지능형 교통체계 표준 노드링크 관리시스템에서 제공하는 네트워크 DB에 대해 A* 알고리즘을 적용하는 각 단계별 과정에 대한 상태정보를 기록한 자료에 대한 글입니다. A*에 대한 자세한 설명은 기존의 최단 경로 탐색 – A* 알고리즘이라는 글을 참고하시기 바랍니다. 본 글은 A* 알고리즘을 확장하고 응용한 글이므로 반드시 A* 알고리즘을 완전하게 이해하고 있는 상태에서만 의미있게 이해될 수 있는 글이라는 점을 알려드립니다.

해결하고자 하는 네트워크 DB에서의 최적경로에 대한 문제는 아래 그림과 같습니다.

위의 문제에 대해 확장된 A* 알고리즘을 통해 최종적으로 도출된 그 결과는 아래의 그림과 같습니다.

해결하고자 하는 문제와 그 결과 도출을 위한 알고리즘의 각 단계를 정리한 내용은 아래의 pdf 파일에 담겨 있습니다.

Java에서 HashMap을 위한 Custom Key 정의하기

Java의 HashMap 자료구조는 고유한 값인 Key와 1:1로 연관되는 값(Value)을 하나의 쌍(Pair)로 하여 여러 개의 쌍을 저장하는 자료구조입니다. 흔히 Key 값으로 문자열이나 정수값을 사용하는 것으로도 충분합니다. 그러나 기능에 따라서 개발자가 특별한 Key에 대한 타입을 정의해야할 필요가 있습니다.

만약 개발자가 Key 타입으로 아래와 같은 클래스를 정의했다고 합시다.

public class Identifier {
    private long A;
    private long B;
    private long C;
	
    public Identifier(long A, long B, long C) {
        this.A = A;
        this.B = B;
        this.C = C;
    }
}

그리고 위의 Identifier 클래스를 Key로 하는 HashMap을 사용하는 코드를 아래와 같이 작성했다고 합시다.

HashMap<Identifier, String> map = new HashMap<Identifier, String>();
		
Identifier id1 = new Identifier(1,2,3);
map.put(id1, "White");
		
Identifier id2 = new Identifier(2,1,3);
map.put(id2, "Yellow");
		
Identifier id3 = new Identifier(2,1,3);
map.put(id3, "Red");
		
System.out.println(map.get(id2));

Key로써의 Identifier 클래스의 동일성을 Identifier가 가지는 3개의 필드인 A, B, C의 동일함으로 정의한다고 할때, 두개의 Identifier 객체 o1, o2가 있다고 한다면 o1과 o2가 같을 조건은 o1.A == o2.A && o1.B == o2.B && o1.C == o2.C라고 할 수 있습니다. 이러한 정의대로라면 위의 코드의 결과는 new Identifier(2,1,3)으로 생성된 객체 중 가장 마지막으로 저장된 “Red” 문자열을 출력해야 하는데, “Yellow”를 출력하게 됩니다. 그렇다면 원하는 결과를 얻기 위해서 무엇을 해야 할까요?

HashMap의 Key 타입으로써 기능을 충족하기 위해서는 Object 클래스의 equals와 hashCode 함수를 재정의해야 합니다. 아래가 Identifier 클래스에 이 2개의 함수를 재정의한 전체 코드입니다.

import java.util.Objects;

public class Identifier {
    private long A;
    private long B;
    private long C;
	
    public Identifier(long A, long B, long C) {
        this.A = A;
        this.B = B;
        this.C = C;
    }
	
    @Override
    public boolean equals(Object o) {
        if(this == o) return true;	
        if(o == null || getClass() != o.getClass()) return false;
		
        Identifier other = (Identifier)o;
		
        return other.A == A && other.B == B && other.C == C;
    }
	
    @Override
    public int hashCode() {
        int result = (int)(A ^ (A >>> 32));
        
        result = 31 * result + (int)(B ^ (B >>> 32));
        result = 31 * result + (int)(C ^ (C >>> 32));
		
        return result;
    }
}

위의 equals 함수는 앞서 설명한 동일성에 대한 정의를 코드로 작성한 것입니다. hashCode 함수는 최대한 중복되지 않고 다양한 값을 반환할 수 있어야 함과 동시에 같은 값이라고 한다면 같은 hashCode 값을 반환해야 합니다. 즉, new Identifier(2,1,3)를 2번 호출하여 2개의 객체를 생성했다면 이 2개의 객체에 대한 hashCode 함수의 반환값은 같아야 합니다. 이러한 조건을 최대한 수용할 수 있도록 작성했다고 작성한게 위의 hashCode 함수의 구현입니다만… 이게 애매한지라 hashCode 함수에 대해서만큼은 아래의 코드로 대체할 수 있습니다.

@Override
public int hashCode() {
    return Objects.hash(A, B, C); // import java.util.Objects; 가 필요함
}

이제 실행해 보면, 우리가 원하는 결과를 얻을 수 있습니다. 이상으로 HashMap에 대한 Custom Key 타입을 정의하는 방식에 대한 정리였습니다.