[Spring4] 스프링에서 마이바티스(Mybatis) 사용하기

[Spring4] 스프링에서 마이바티스(Mybatis) 사용하기

* 본 게시물은 http://addio3305.tistory.com/62 을 매우 많이 참고하여 작성되었다. 상세한 설명은 이쪽에 잘 나와있다.

본 게시물은 MySQL을 사용한다는 전제로 작성되었으므로, 오라클에 대해서는 위 게시물을 참고 바란다.

본 게시물은 [Spring4] STS에서 스프링 웹 프로젝트 생성 (http://blog.naver.com/bb_/221339454799) 포스팅에 이어서 곧바로 적용시킬 수 있는 예제이다. 다시 말해 STS 설치부터 마이바티스 연동까지 한 큐에 해결할 수 있다.

스프링에 마이바티스를 붙이려면 파일을 10개 정도 고쳐야 한다.

1. pom.xml 에 디펜던시 추가

(/프로젝트폴더/pom.xml)

        <!– DB –>
        <dependency>
        <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.2.2</version>
        </dependency>
       
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.2.0</version>
        </dependency>
       
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>${org.springframework-version}</version>
        </dependency>
       
        <dependency>
            <groupId>commons-dbcp</groupId>
            <artifactId>commons-dbcp</artifactId>
            <version>1.4</version>
        </dependency>
   
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>6.0.6</version>
        </dependency>

2. web.xml 에 컨텍스트 파람 추가

(/프로젝트폴더/src/main/webapp/WEB-INF/web.xml)

    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath*:config/spring/context-*.xml</param-value>
    </context-param>

3. context-datasource.xml 작성

(/프로젝트폴더/src/main/resources/config/spring/context-datasource.xml)

* 상위 폴더가 존재하지 않을 경우, 경로에 맞게 만들어줘야 한다.

<?xml version=“1.0” encoding=“UTF-8”?>
<beans xmlns=http://www.springframework.org/schema/beans
xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
xsi:schemaLocation=http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd
>
<bean id=“dataSource” class=“org.apache.commons.dbcp.BasicDataSource” destroy-method=“close”>
<property name=“driverClassName” value=“com.mysql.jdbc.Driver”/>
<property name=“url” value=“jdbc:mysql://아이피주소:포트/디비명”/>
<property name=“username” value=“아이디”/>
<property name=“password” value=“비밀번호”/>
</bean>
</beans>

여기서 url 프로퍼티 뒤에 물음표를 붙이고 필요한 파라미터를 더 추가할 수 있다.

개인적으로는 아래와 같이 기입해서 사용하고 있다.

<property name=“url” value=“jdbc:mysql://아이피주소:포트/디비명?userUnicode=true&amp;characterEncoding=utf8&amp;useJDBCCompliantTimezoneShift=true&amp;useLegacyDatetimeCode=false&amp;serverTimezone=UTC”/>

xml이므로 앤드 기호를 &amp; 로 써야 한다.

4. context-mapper.xml 작성

(/프로젝트폴더/src/main/resources/config/spring/context-mapper.xml)

* 상위 폴더가 존재하지 않을 경우, 경로에 맞게 만들어줘야 한다.

<?xml version=“1.0” encoding=“UTF-8”?>
<beans xmlns=http://www.springframework.org/schema/beans
    xmlns:xsi=http://www.w3.org/2001/XMLSchema-instance
    xmlns:context=http://www.springframework.org/schema/context
    xsi:schemaLocation=http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
>
    <bean id=“sqlSession” class=“org.mybatis.spring.SqlSessionFactoryBean”>
        <property name=“dataSource” ref=“dataSource” />
        <property name=“mapperLocations” value=“classpath:/mapper/**/*-mapper.xml” />
    </bean>

    <bean id=“sqlSessionTemplate” class=“org.mybatis.spring.SqlSessionTemplate”>
        <constructor-arg index=“0” ref=“sqlSession”/>
    </bean>
</beans>

5. test-mapper.xml 작성

(/프로젝트폴더/src/main/resources/mapper/test/test-mapper.xml)

<?xml version=“1.0” encoding=“UTF-8”?>
<!DOCTYPE mapper PUBLIC “-//mybatis.org//DTD Mapper 3.0//EN” http://mybatis.org/dtd/mybatis-3-mapper.dtd>

<mapper namespace=“test”>

    <!– <select id=“selectCount” parameterType=“hashmap” resultType=“hashmap”> –>
    <select id=“selectCount” resultType=“int”>

    <![CDATA[
         SELECT COUNT(*) FROM test

    ]]>

</select>

</mapper>

* 이 xml 파일에는 잘 작동하는 쿼리를 적어넣어야 한다. 현재 test라는 테이블의 로우 개수를 가져오고 있는데, 아마 이 글을 읽으시는 독자 분의 DB에는 test 라는 테이블이 없을 것이다. 테스트할 수 있도록 test 라는 테이블을 새로 만들든지, 존재하는 테이블 이름으로 바꿔서 적어주시길 바란다.

6. AbstractDAO.java 작성
(/프로젝트폴더/src/main/java/com/bb/nddoc/common/dao/AbstractDAO.java)

package com.bb.nddoc.common.dao;

import java.awt.List;
import org.mybatis.spring.SqlSessionTemplate;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

public class AbstractDAO {
    private static final Logger logger = LoggerFactory.getLogger(AbstractDAO.class);

    @Autowired
    private SqlSessionTemplate sqlSession;

    protected void printQueryId(String queryId) {
        logger.info(“QueryId : “ + queryId);
    }

    public Object insert(String queryId, Object params) {
        printQueryId(queryId);
        return sqlSession.insert(queryId, params);
    }

    public Object update(String queryId, Object params) {
        printQueryId(queryId);
        return sqlSession.update(queryId, params);
    }

    public Object delete(String queryId, Object params) {
        printQueryId(queryId);
        return sqlSession.delete(queryId, params);
    }

    public Object selectOne(String queryId) {
        printQueryId(queryId);
        return sqlSession.selectOne(queryId);
    }

    public Object selectOne(String queryId, Object params) {
        printQueryId(queryId);
        return sqlSession.selectOne(queryId, params);
    }

    @SuppressWarnings(“rawtypes”)
    public List selectList(String queryId) {
        printQueryId(queryId);
        return (List) sqlSession.selectList(queryId);
    }

    @SuppressWarnings(“rawtypes”)
    public List selectList(String queryId, Object params) {
        printQueryId(queryId);
        return (List) sqlSession.selectList(queryId, params);
    }
}

7. TestDAO.java 작성
(/프로젝트폴더/src/main/java/com/bb/nddoc/dao/test/TestDAO.java)

package com.bb.nddoc.dao.test;

import org.springframework.stereotype.Repository;
import com.bb.nddoc.common.dao.AbstractDAO;

@Repository(“testDAO”)
public class TestDAO extends AbstractDAO {

    public int selectCount() throws Exception {

        // 여기서 test.selectCount 란, 네임스페이스가 test 인 *-mapper.xml 내부의, id 가 selectCount 인 쿼리를 의미한다.

        // 즉, test-mapper.xml 파일에 담긴 쿼리를 뜻한다.​
        return (Integer) selectOne(“test.selectCount”);
    }
}

8. TestModel.java 작성
(/프로젝트폴더/src/main/java/com/bb/nddoc/model/test/TestModel.java)

package com.bb.nddoc.model.test;

import javax.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import com.bb.nddoc.service.test.TestServiceImpl;

@Controller
public class TestModel {
    
private static final Logger logger = LoggerFactory.getLogger(TestModel.class);
    
    @Resource(name=“testService”)
    private TestServiceImpl testService;
    
    @RequestMapping(value = “/test”, method = RequestMethod.GET)
    public String test(Model model) {
        
        try {
            int result = testService.select();
            logger.info(“result : “ + result);
        
        } catch (Exception e) {
            logger.error(e.getMessage());
            e.printStackTrace();
        }
        
        return “home”;
    }
}

9. TestService.java 작성

(/프로젝트폴더/src/main/java/com/bb/nddoc/service/test/TestService.java)

package com.bb.nddoc.service.test;

public interface TestService {

    public int select() throws Exception;
}

10. TestServiceImpl.java 작성

(/프로젝트폴더/src/main/java/com/bb/nddoc/service/test/TestServiceImpl.java)

package com.bb.nddoc.service.test;

import javax.annotation.Resource;

import org.springframework.stereotype.Service;

import com.bb.nddoc.dao.test.TestDAO;

@Service(“testService”)
public class TestServiceImpl implements TestService {

    @Resource(name=“testDAO”)
    private TestDAO testDAO;
    
    public int select() throws Exception {
        return testDAO.selectCount();
    }
}

새로 작성하거나 수정한 파일의 계층구조는 다음과 같다. (10개 파일)

많은 파일을 고쳤지만 앞으로도 그래야 하는 것은 아니다.

pom.xml, web.xml, context-datasource.xml, context-mapper.xml, AbstractDAO.java 은 최초 한 번만 수정/작성하면 된다.

새로운 SQL 세트가 필요할 때에는 test-mapper.xml, TestDAO.java, TestModel.java, TestService.java, TestServiceImpl.java 을 적당히 복사해서 만들면 된다.

예를 들면, board-mapper.xml, BoardDAO.java, BoardModel.java, BoardService.java, BoardServiceImpl.java 식으로 만들면 된다.

멀티파트 문자열 파라미터 깨지는 문제 (트러블 슈팅 기록)

멀티파트 문자열 파라미터 깨지는 문제 (트러블 슈팅 기록)

어떤 메일연동 시스템이 있다.
특정 페이지에 제목, 내용, 첨부파일들을 멀티파트 형태(enctype=”multipart/form-data”)로 쏘면, 메일을 생성하고 발송까지 해주는 메일연동이다.

기존에 이 페이지를 잘 쓰고 있었는데, 새로운 업체가 들어와서 사용해보니 글자가 깨져나온다고 했다.
새 업체는 문자열을 왜 이런 문제가 생기는지 알 수 없다고 했다.
문제는 문자열을 UTF-8로 인코딩해서 보냈기 때문이었다.

몇 가지 테스트를 통해 확인한 결과는 다음과 같다.

1. 원래 한글을 그냥 넘겨도, WAS 설정이 잘되어 있다면 와스가 알아서 UTF-8로 만들어준다.

예를 들어 window.open(“http://test.co.kr/test.jsp?param=한글“); 식으로 웹페이지를 열었을 때,
받는 쪽인 test.jsp 페이지에서
String param = request.getParameter(“param”);
System.out.println(“param : ” + param);
해보면 콘솔에 “param : 한글” 과 같이 잘 찍힌다.

WAS 설정이 잘 안되어 있다면 (그리고 WAS 설정과 무관하게 잘 돌게 코딩하려면),
클라이언트에서 쏠 때
var param = encodeURIComponent(“한글”);
var targetUrl = “http://test.co.kr/test.jsp?param=” + param;
window.open(targetUrl);
위와 같이 코딩할텐데, 이 역시 콘솔에 “param : 한글” 과 같이 잘 찍힌다.

2. 그런데 해당 페이지는 멀티파트 형태로 submit 해야 동작하도록 설계되어 있었다.

따라서 request.getParameter() 코드로 파라미터를 가져오지 않는 상태였다.

이 경우 파라미터는, 서버에서 request 객체 안에 있는 InputStream 을 readLine() 해서 얻어오게 되는데,
살펴보니 얻어온 파라미터 값을 항상 UTF-8 로 바꾸도록 코딩되어 있었다.
cf) String newStr = new String(oldStr.getBytes(“8859_1”), “UTF-8”);

테스트 결과 form 내 input 태그의 value 를 pure한 한글로 지정했을 때,
서버에서 UTF-8로 잘 바꿔주는 것을 확인할 수 있었다.

그런데 문제가 된 경우는, form 내 input 태그의 value 를 미리 UTF-8로 변환한 경우였다.
예를 들어 input1.value = encodeURIComponent(“한글”); 이라고 코딩한 경우 서버에서 깨져버리는 것을 확인할 수 있었다.
UTF-8로 넘어온 값을 ISO-8859-1로 getBytes하고, 다시 UTF-8로 인코딩하기 때문으로 보인다.

3. 아마 보내는 쪽에서 파라미터마다 encodeURIComponent() 로 감싸주었거나,
<form … enctype=”multipart/form-data”> 가 아닌
<form … enctype=”multipart/form-data” accept-charset=”UTF-8″> 와 같이 코딩한 것으로 추측된다.

cf) accept-charset 에는 서버로 전송할 인코딩을 쓴다. 자바스크립트에서는 아래와 같이 코딩하기도 한다.
form.acceptCharset = “UTF-8”;
document.charset = form.acceptCharset;

파라미터를 UTF-8로 보내는 일이 잘못된 일은 아니지만 (오히려 코딩을 잘했다고 볼 수 있지만)
멀티파트를 받아 처리하는 서버 쪽이 무조건 ISO-8859-1 로 getBytes 해서 UTF-8 로 인코딩하게 코딩되어 있으므로,
pure하게 파라미터를 보내달라고 요청하였다.

윈도우 10 파란색 화살표

윈도우 10 파란색 화살표

윈도우 10 에서 아이콘 우측상단에 파란색 화살표가 붙는 현상이 있다. (아래 그림 참조)

 

이 경우 아이콘 마우스 우클릭 – [속성] – 속성 창에서 [고급] 버튼 클릭 – 고급 창에서 [내용을 압축하여 디스크 공간 절약 체크 해제] 하고 [확인] 버튼을 클릭하면, 아이콘 우측 상단의 파란색 화살표가 사라진다.

 

NTFS 파일 시스템 내의 파일들을 윈도우 10 이 압축시키는 것으로 보이며, 굳이 파란색 화살표를 없애지 않아도 파일을 사용하는 데에는 아무 지장이 없다.

하지만 눈에 보이면 거슬려서 보이는 족족 지우고 있는데… 혹시 C 드라이브 전체적으로 향후 적용되지 않도록 하는 방법을 아시는 분이 있으면 댓글로 달아주시길 바란다. (2018. 10. 31)

댓글 제보에 따르면, C 드라이브 등 다른 곳들은 문제가 되지 않고, 유독 바탕화면, 내 문서 등만이 디스크 공간 절약 설정이 되어 있는 경우 해결책. [C:\Users\이름] 에 들어가면 바탕화면, 내 문서, 다운로드 등 폴더들이 있다. 여기서 [디스크 공간 절약 설정] 체크 해제하면 된다. 바탕화면 상위에서 해결하는 방법이다. (2019. 07. 19)

익스플로러 항상 새 세션으로 열기 적용하는 방법

익스플로러 항상 새 세션으로 열기 적용하는 방법

Internet Explorer 항상 새 세션으로 열리도록 적용하는 방법이다.

바로가기 아이콘 우클릭 – [속성] – 속성창의 [대상] 값 뒤쪽에 -nomerge 를 붙이면 항상 새 세션으로 열린다.

웹사이트 개발시 여러 개의 아이디로 테스트할 경우 유용하다. (세션이 꼬이지 않는다.)

자바 volatile 예약어

자바 volatile 예약어

엊그제 volatile[볼러타일]이라는 예약어를 처음 알게 됐다. 자바를 쓴지가 몇 년인데 아직 모르는게 많다…

왜 모르는가 생각해보니 웹 프로그램에서는 쓸 일이 없는 예약어였다. 이번에는 게임 프로그래밍 책을 읽다가 알게 됐다.

변수 앞에 volatile 을 붙이면 메인 메모리에 변수를 읽고 쓴다. 기본적으로 JMM(자바 메모리 머신)은 쓰레드 각각이 로컬 메모리를 쓰도록 만든다. volatile을 쓰면 메인 메모리 한곳을 바라보므로 안정적이다. 물론 값을 읽고 쓰는 비용은 더 높다.

게임 프로그램은 FPS(초당 프레임)가 높고 각각의 쓰레드가 끊임없이 돌아가므로 메인 메모리와 로컬 메모리의 복제의 동시성을 잃고 불일치가 일어날 수 있다. 특히 무한루프의 on/off와 관련된 변수는 volatile을 넣어주는 것 같다.

window.showModalDialog / window.showModelessDialog

window.showModalDialog / window.showModelessDialog

IE4에서부터 window.open 대신 사용할 수 있었던 코드.

window.alert 함수나 window.confirm 함수처럼 일단 쇼모달 코드가 실행되면 해당 윈도우가 닫힐 때까지 다음줄로 진행되지 않는다.
설명했다시피 동기식이므로 절차적으로 이해하기/작성하기 쉽다는 장점이 있다.

showModalDialog 는 뒤쪽 윈도우을 클릭할 수 없게 통제되고, showModelessDialog 는 뒤쪽 윈도우를 클릭 가능하다.

과거에는 많이 썼을지 몰라도 사용금지이며, 걷어내야할 대상이다.
각종 모바일이나 크롬 브라우저 등에서 작동하지 않는 코드, 즉 표준이 아닌 코드이므로 사실상 사용금지다.

1. window.showModalDialog

window.showModalDialog(주소, 넘겨줄객체, 창옵션);

위와 같이 쓴다. 예를 들어 아래와 같이 쓴다.

var url = “collee.jsp”;
var paramObj = {};
var option = “center:yes; status:no; help:no; resizable:no; scroll:no; dialogWidth:300px; dialogHeight:200px;”;

var result = window.showModalDialog(url, paramObj, option);

2. window.dialogArguments

쇼모달 창으로 열린 페이지(ex: collee.jsp)에서는 window.dialogArguments 로 paramObj를 받는다.
예를 들어 window.onload 함수 안에서 var inputObj = window.dialogArguments; 이런식으로 받을 수 있다.

이 때 참조값을 바라보므로, inputObj.aaa = “newValue”; 식으로 코딩하면,

오프너 페이지(ex: coller.jsp)의 paramObj 객체를 조작 가능하다는 장점이 있다.

3. window.returnValue

또한 쇼모달 창으로 열린 페이지(ex: collee.jsp)에서 window.returnValue 값을 부여하면, 창이 닫혔을 때 해당값이 리턴된다.

예를 들어 오프너 창에서는 var result = window.showModalDialog(url, paramObj, option); 라는 코드로 쇼모달 창을 열고,

쇼모달 창 내에서 window.returnValue = “111”; 이라고 코딩했다면,

쇼모달 창을 닫을 경우 오프너의 var result 변수에 값 “111”이 대입된다.

[TOMCAT] java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

[TOMCAT] java.lang.IllegalArgumentException: Invalid character found in the request target. The valid characters are defined in RFC 7230 and RFC 3986

■ 오류 내용

서비스를 톰캣에 띄웠을 때 400 에러가 나는 경우가 있다. 400은 404와 다르다.

404는 페이지를 찾지 못하는 경우고, 400은 신택스(문법) 에러다. 쉽게 말해 파싱 실패다.

검색결과 아래 버전들에서 해당 현상이 발견되고 있다.

– Tomcat 7.0.73 버전

– Tomcat 8.0.39 버전

– Tomcat 8.5.31 버전

– Tomcat 8.5.7 버전

– Tomcat 9.0.8 버전

참고 1) Tomcat 8.5.3 에러 메시지

Ensure that requests with HTTP method names that are not tokens (as required by RFC 7231) are rejected with a 400 response

참고 2) Tomcat 8.5.7 에러 메시지

Add additional checks for valid characters to the HTTP request line parsing so invalid request lines are rejected sooner.

* http://stackoverflow.com/questions/41053653/tomcat-8-is-not-able-to-handle-get-request-with-in-query-parameters/44005213#44005213

* http://stackoverflow.com/questions/50361171/how-to-allow-character-in-urls-for-tomcat-8-5

■ 원인

보안상의 이유로 톰캣이 URL에 특수문자가 들어간 경우를 잡아내는 것이다.

해결방법은 아래와 같다.

■ 문제해결

1. 특수문자 인코딩하기

자바스크립트에서 encodeURI 또는 encodeURIComponent 로 주소를 인코딩하면 된다.

encodeURI 는 주소 전체를 인코딩하는 경우 사용하면 되고, encodeURIComponent 는 개별 파라미터를 인코딩하는 경우 사용하면 된다.

특수문자는 인코딩해주고, 한글은 웬만하면 제거하도록 한다.

* Javascript encodeURI 와 encodeURIComponent 차이 (http://blog.naver.com/bb_/221047463324)

2. POST 방식으로 호출하기

GET 방식으로 호출하던 것을 POST 방식으로 바꾼다.

3. 톰캣 설정값을 변경하기

relaxedPathChars, relaxedQueryChars 를 수정하면 된다고 한다.

* http://tomcat.apache.org/tomcat-8.5-doc/config/http.html 페이지에서 relaxedPathChars, relaxedQueryChars 검색

BBClassMerge : 자바 클래스 머지를 위한 폴더용 디컴파일러

BBClassMerge : 자바 클래스 머지를 위한 폴더용 디컴파일러

2018년 9월 26일에 업로드했던 BBDirDecompiler(폴더용 자바 디컴파일러)를 발전시킨 프로그램이다.

classmerge 패키지와 diff 패키지로 구성되어 있는데, 크게 두 가지 프로그램이 합쳐져 있다고 보면 된다.

classmerge 패키지 쪽은 이번에 작성한 것이다.

diff 패키지 쪽은 2017년 1~2월에 작성했던 것을 코드 보완해서 녹인 것이다.

■ 다운로드

첨부파일 BBClassMerge_181022.zip 를 다운받으면 된다.

실행파일은 bin 폴더 안에 있으며, 소스파일은 sources 폴더 안에 있다.

■ 이 프로그램의 목적

class 파일들의 내용을 비교하고(Diff), 합치기(Merge) 위한 프로그램이다.

예를 들어 운영서버에 있는 class 파일들과 로컬에 있는 class 파일들이 서로 다를 때 사용할 수 있다.

■ 사용법

Left Classes Dir 이 기준이 되는 폴더위치다.

예컨대 Left Classes Dir 에 운영서버의 classes 파일이 들어있는 폴더 경로를 입력하면 된다.

Right Classes Dir 은 비교를 위한 폴더위치다.

정확히 표현하면, 현재 갖고 있는 자바 소스가 Right Classes Dir 인데, 궁극적으로 Left Classes Dir 의 소스 코드로 만들고자 할 때 이 프로그램을 사용하면 된다.

1. Decompile 버튼

Left Classes Dir 폴더 내의 class 파일들과, Right Classes Dir 폴더 내의 클래스 파일들을 폴더째 디컴파일한다.

확장자가 class 인 파일들만 처리한다.

최종적으로 2개 폴더가 생성된다.

하나는 Left Classes Dir 폴더를 디컴파일한 결과고, 다른 하나는 Right Classes Dir 폴더를 디컴파일한 결과다.

2. Merge 버튼

Left Classes Dir 폴더 내의 class 파일들과, Right Classes Dir 폴더 내의 클래스 파일들을 머지하기 위한 기능이다.

Right Classes Dir 폴더를 Left Classes Dir 폴더에 가깝게 만들기 위한 기능이다.

2-1. Left Classes Dir 폴더 내에만 존재하는 파일 : 복사한다. (클래스라면 디컴파일해서 복사한다.)

2-2. Right Classes Dir 폴더 내에만 존재하는 파일 : 무시한다. (복사하지 않는다)

2-3. Left Classes Dir 폴더 와 Right Classes Dir 폴더 양쪽에 존재하는 파일 : 두 개 파일을 비교해서, 동일할 경우 무시한다. 상이할 경우 Left Classes Dir 폴더 쪽의 파일을 복사한다. (클래스라면 디컴파일해서 복사한다.)

최종적으로 1개 폴더가 생성된다.

이 결과 폴더를 Right Classes Dir 폴더에 해당되는 자바 소스에 덮어쓰기하면, 궁극적으로 Left Classes Dir 의 소스 코드에 가까워진다.

[같아진다]가 아니라 굳이 [가까워진다]라고 표현한 것은, 아무래도 몇 가지 예외 케이스가 있기 때문이다.

* 예외 케이스

동봉된 jad 디컴파일러의 문제로 디컴파일된 내용에 어셈블리어가 섞여나오는 경우가 있다. 100% 온전하게 디컴파일되지 않는다. 이 경우 소스가 잘 돌아가도록 일일히 수정해줘야 한다.

3. Diff 버튼
Left Classes Dir 폴더 내의 class 파일들과, Right Classes Dir 폴더 내의 클래스 파일들을 비교하기(Diff) 위한 기능이다.

Diff 툴로써의 기능은 떨어지지만, 특장점이 두 가지 있다.

첫번째 장점은 좌측에 폴더 트리를 제공한다는 점이다.

Left Classes Dir 폴더에만 존재하는 파일은 [Left]라고 표시되고, Right Classes Dir 폴더에만 존재하는 파일은 [Right]라고 표시된다.

양쪽 모두에 존재하는 파일은 파일용량 차이가 [용량차이 숫자값]으로 표시된다.

용량 차이가 없을 경우 [0]이라고 표시될 것이다.

두번째 장점은 class 파일을 곧바로 비교할 수 있다는 점이다.

트리 상의 파일을 더블클릭했을 때 class 파일인 경우, 양쪽 파일을 곧바로 decompile 해서 diff하고 상이한 부분을 하이라이트 처리한다.

참고사항. class 파일의 비교

다른 파일들도 그렇지만, class 파일은 용량이 같다고 해서 동일하다고 볼 수는 없다.

텍스트 파일도 글자수가 같으면 용량이 같지만, 내용이 다른 경우가 있듯이 말이다.

문제는 class 파일은 용량이 다르다고 해서 상이하다고 볼 수도 없다.

일반적으로는 용량이 다르면 상이한 파일이라고 볼 수 있는데, class 파일은 디컴파일 해놓고 보면 동일한 내용인 경우가 있다.

버전 등 컴파일러가 다를 경우 class 파일의 용량과 내용이 조금 다를 수 있기 때문이다.

따라서 본 프로그램에서는 Merge 버튼으로 class 파일을 비교할 때, 우선 디컴파일 하지 않은 상태에서 용량과 내용이 동일한지 검사하고, 이어서 디컴파일한 상태에서 용량과 내용이 동일한지까지 검사한다.

[JAVA] 프로퍼티 파일 읽기 직접 구현 (PropertiesUtil 클래스의 readPropertiesFile 메서드)

[JAVA] 프로퍼티 파일 읽기 직접 구현 (PropertiesUtil 클래스의 readPropertiesFile 메서드)

자바에서 정석적인 프로퍼티 파일 읽기​는 아래 포스트를 참고하면 된다.

https://blog.naver.com/bb_/221666064467

위 링크는 정석적인 프로퍼티 파일 읽기이고, 본 포스트는 프로퍼티 파일 읽기를 직접 구현해본 것이다.

// 프로퍼티 파일 읽기 (PropertiesUtil 클래스의 readPropertiesFile 메서드)

// UTF-8 인코딩 형식의 properties 파일을 읽어서 HashMap 객체로 만들어 리턴하는 메서드.

package com.bb.classmerge.util; 

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.HashMap;

public class PropertiesUtil {

    /**
     * UTF-8 인코딩 형식의 properties 파일을 읽어서 HashMap 객체로 만들어 리턴한다.
     *
     * @param propFilePath
     * @return
     * @throws Exception
     */

    public static HashMap<String, String> readPropertiesFile(String propFilePath) throws Exception {
        if (propFilePath == null || propFilePath.length() == 0) {
            throw new Exception(“PropertiesUtil readPropertiesFile : propFilePath == null || propFilePath.length() == 0”);
        }

        File propFileObj = new File(propFilePath);
        if (!propFileObj.exists()) {
            throw new Exception(“PropertiesUtil readPropertiesFile : propFileObj does not exists. [“ + propFileObj.getAbsolutePath() + “]”);
        }
        
        if (!propFileObj.canRead()) {
            throw new Exception(“PropertiesUtil readPropertiesFile : propFileObj can not read. [“ + propFileObj.getAbsolutePath() + “]”);
        }

        HashMap<String, String> resultMap = new HashMap<String, String>();

        FileInputStream fileInputStream = null;
        InputStreamReader inputStreamReader = null;
        BufferedReader bufferedReader = null;

        try {
            fileInputStream = new FileInputStream(propFileObj);
            inputStreamReader = new InputStreamReader(fileInputStream, “UTF-8”);
            bufferedReader = new BufferedReader(inputStreamReader);

            String oneLine = null;
            while ((oneLine = bufferedReader.readLine()) != null) {
                if (oneLine == null || oneLine.length() == 0) {
                    continue;
                }
                
                // 주석 무시
                if (oneLine.trim().startsWith(“#”)) {
                    continue;
                }
                
                int equalIndex = oneLine.indexOf(“=”);
                if (equalIndex < 0) {
                    continue;
                }
                
                // 좌측값(key값)만 trim 처리한다. 우측값(value값)은 의도적으로 공백이 포함될 수 있다고 판단한다.
                String leftText = oneLine.substring(0, equalIndex).trim();
                String rightText = oneLine.substring(equalIndex + 1);
                
                // 등호 좌측 텍스트가 존재하지 않을 경우 무시
                if (leftText.length() == 0) {
                    continue;
                }
                
                resultMap.put(leftText, rightText);
            }

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            try {
                if (bufferedReader != null) {
                    bufferedReader.close();
                }
            } catch (Exception e) {
                // 무시
            } finally {
                bufferedReader = null;
            }

            try {
                if (inputStreamReader != null) {
                    inputStreamReader.close();
                }
            } catch (Exception e) {
                // 무시
            } finally {
                inputStreamReader = null;
            }

            try {
                if (fileInputStream != null) {
                    fileInputStream.close();
                }
            } catch (Exception e) {
                // 무시
            } finally {
                fileInputStream = null;
            }
        }

        return resultMap;
    }
}

[Android] x86 emulation currently requires hardware acceleration

[Android] x86 emulation currently requires hardware acceleration

안드로이드 스튜디오에서 첫번째 프로젝트를 실행시켜 보려는데 뜨는 에러.

x86 emulation currently requires hardware acceleration.

1. 우선 안드로이드 스튜디오 상단 메뉴 – [Tools] – [SDK Manager] 의 [SDK Tools] 탭에서 [Intel x86 Emulator Accelerator (HAXM installer)] 를 설치했다.

2. 그래도 소용이 없어서 아래 주소로 들어가서 관련 프로그램을 다운받아 설치했다.

Intel® Hardware Accelerated Execution Manager (Intel® HAXM)

https://software.intel.com/en-us/articles/intel-hardware-accelerated-execution-manager-intel-haxm

3. 그래도 소용이 없어서 바이오스 환경으로 윈도우 재시작하여 가상화 관련 항목을 enable 해주었다.

참고로 필자의 컴퓨터는 LENOVO ideapad 320s 이며 운영체제는 Windows 10 Pro 64비트이다.

재부팅 – 바이오스 접근 – [Configuration] – [Intel Virtual Technology] 를 [Enabled]로 변경했다.

* CPU에 따라 가상화 관련 옵션명은 다를 수 있다. Configuration 이 아닌 Security 에 있을 수도 있음.

 

참고로 Windows 10 에서 바이오스로 재부팅하는 방법은 다음과 같다.

4. 잘된다.

스프링 타일즈 관련 메모

스프링 타일즈 관련 메모

훗날 내가 이 글을 읽고 이해할 수 있을지 의문이지만…

조만간 오늘 일을 까먹을 것은 분명하기에 여기 기록해둔다.

회사 포탈 솔루션에 한정해서 서술한다.

/*/* 패턴, 예를 들어 /aaa/bbb 같은 패턴의 경우,

구현된 뷰 리졸버 내용상, 타일즈를 거쳐서 jsp 파일로 도달하게 된다.

코드로 하면 이렇다.

ModelAndView mv = new ModelAndView(“/aaa/bbb”);

참고로 ModelAndView 객체 생성자의 인자값은 viewName 이다.

컨트롤러에서 해당 ModelAndView 를 리턴하게 되면,

뷰 리졸버가 타일즈를 거쳐 jsp로 도달하게 만든다.

그런데 viewName 이 .jstl 또는 .json 으로 끝날 경우 타일즈를 거치지 않는 것이 아닌가.

예를 들어 “/aaa/bbb.jstl” 또는 “/aaa/bbb.json” 은 타일즈를 거치지 않고 jsp에 도달한다.

이런저런 시도를 해보니 구현된 뷰 리졸버가 3가지였다.

타일즈를 쓰는 일반적인 뷰 리졸버 하나,

jstl용 뷰 리졸버,

json용 뷰 리졸버 이렇게 3가지였다.

일단 스프링이 ModelAndView 의 viewName 을 이용해 뷰 리졸버 매핑을 시도하는데,

/*/* 패턴은 마침표(.)를 매핑하지 못하는 것 같다.

예를 들어 “/aaa/bbb.ccc”라고 viewName을 쓰면

어떤 리졸버에도 매핑되지 못하여 서버 에러인 500 에러가 발생한다.

“/aaa/bbb” 라고 쓰면 일반적인 뷰 리졸버에 매핑되고,

“/aaa/bbb.jstl” 이라고 쓰면 jstl용 뷰 리졸버에 매핑되고,

“/aaa/bbb.json” 이라고 쓰면 json용 뷰 리졸버에 매핑된다.

“/aaa/bbb.ccc” 라고 쓰면 500 에러가 발생한다.

찾아보니 xml 파일과 properties 파일에 뷰 리졸버 관련 설정이 들어있었다.

application 어쩌고 context 저쩌고라는 이름의 properties 파일에 jstl, json 등 확장자에 따른 설정이 들어있었고,

어떤 xml 파일에도 관련 설정이 들어있었다.

자세한 설정 방법은 프로젝트 내에서 문자열 “jstlView” 또는 “jsonView” 를 *.properties 와 *.xml 파일들을 대상으로 검색해보면 알 수 있다.

끝.

모바일 Push Server의 구성

모바일 Push Server의 구성

내가 다니는 회사에서 Push Server 라고 부르는 것은 사실 Push 중계서버를 뜻한다.

Push Server 관련 플로우는 아래와 같다.

[모바일기기 -> 제품서버 -> Push 중계서버(Node.js) -> FCM 또는 APNS -> 모바일기기]

* FCM은 Firebase Cloud Messaging 의 약자다. 구글의 안드로이드용 Push 서버라고 할 수 있다. 원래 이름은 GCM (Google Cloud Messaging)이었다. 이후 구글이 파이어베이스를 인수하면서 기존 GCM 이 Deprecated[디프리케이티드]되고 FCM 으로 이름이 변경되었다. FCM 은 안드로이드, iOS, Mobile Web 등의 플랫폼들을 커버한다.

* APNS는 Apple Push Notification Service 의 약자다. 애플의 iOS용 Push 서버라고 할 수 있다.

* 이외에 private 서버가 존재한다. FCM이나 APNS가 아닌 자체 개발한 Push 서버를 뜻한다. 예를 들어 국내에는 Morpheus PUSH[모피어스 푸시]가 있다.

1. 모바일 사용자가 어떤 앱을 켜서 기능을 사용하면, 아이디 등 데이터가 제품서버를 거쳐 Push 중계서버까지 도달한다.

2. Push 중계서버는 안드로이드일 경우 FCM 으로, 아이폰일 경우 APNS 로 (1)토큰 과 (2) 메시지를 보낸다. 토큰은 MariaDB/MongoDB 등으로부터 select해온다.

3. 해당 Push 서버(FCM 또는 APNS)는 토큰을 까서 기기의 정보를 알아내고, 해당 기기로 메시지를 Notify 해준다.

물론 Notify 를 받기 위해서는 해당 앱에 Receive 관련 메서드가 구현되어 있어야 한다.

BBPathPrinter : 특정폴더 하위의 모든파일경로 출력

BBPathPrinter : 특정폴더 하위의 모든파일경로 출력

 

 

특정 폴더경로를 입력하면 해당 폴더 하위의 모든 파일경로를 출력하는 프로그램을 만들었다.

동료 엔지니어가 패치를 위해 각각의 파일 경로를 한땀한땀 따내고 메모장에 정리하는 모습을 보고 만들었다.

단순히 하위경로를 출력해주는 별거 아닌 기능이어서 이걸 꼭 만들어야 하나 다른 엔지니어들에게도 조언을 구했다.

경로를 따내는 일이 귀찮지만 그렇게 오래 걸리는 일이 아니라고 대답하길래,

생각 이상으로 자주 반복하는 작업임을 알았고 꼭 만들어야겠다고 다짐했다.

■ 다운로드

첨부파일 BBPathPrinter_181005.zip 를 다운받으면 된다.

실행파일은 bin 폴더 안에 있으며, 소스파일은 sources 폴더 안에 있다.

■ 이 프로그램의 목적

패치하려는 폴더 경로를 입력해서 하위 파일 경로를 전부 얻는다.

단, 확장자가  java인 파일과 class인 파일은 우선적으로 상위에 표시되며, 나머지 경로는 알파벳 순으로 정렬된다.

기본 딜리미터가 [역슬래시+엔터]로 되어 있다. 아래와 같은 명령어로 리눅스 상에서 백업용 압축파일을 생성해내기 용이하기 때문이다.

tar -cvf 경로1\

경로2\

경로3\

경로4\

경로5

■ 사용법

1. 프로그램을 실행한다. BBPathPrinter_181005.bat 를 더블클릭하면 실행된다.

 

 

2. 상단 인풋박스(Dir Path)에 패치 파일들이 들어있는 폴더 경로를 입력한다.

주의할 점은 파일 경로가 아닌 폴더 경로를 입력해야 한다는 점이다.

 

 

3. [PRINT] 버튼을 누르면 아래와 같이 결과 텍스트 파일이 뜰 것이다.

실행파일(bat파일 또는 jar파일)이 있는 위치 output폴더 안에 txt 파일이 하나씩 쌓이는 구조다.

 

 

Dodging : 자바게임 예제

Dodging : 자바게임 예제

프로그램을 만든 목적은 퓨어 자바에서 게임을 만들기 위해

작성했던 이미지 처리 코드, 키입력 코드를 예제로 보존하기 위함.

방향키를 눌러 캐릭터를 이동하는 기능만 지원한다.

Dodging(피하기) 이라는 제목에서 알 수 있듯이 처음엔 똥피하기 같은 고전적인 피하기 게임을 만드려고 했는데

괜히 소스만 더 복잡해질 것 같아서 여기까지 한다.

퓨어 자바로 짜다보니 아무래도 그래픽 처리가 느리다.

제대로 만들려면 DirectX를 사용하거나, OpenGL을 사용하거나, 기타 게임엔진을 사용해서 그래픽을 표시해야 하겠다.

현재 그래픽 처리 방식으로는 로그라이크나 비주얼노벨까지가 한계일듯.

만약 이 소스를 기반으로 게임을 만들고 싶다면,

지금처럼 표면(Surface) 하나를 1초에 60번 출력하는 식(FPS 60)으로 하지 말고,

이미지 객체 등 컴포넌트들을 여러 개 만들어서 폼 위에 올리는 수 밖에 없다.

키입력 처리는 제법 잘 되어 있다.

BBPatchHelper : 자바 패치도구

BBPatchHelper : 자바 패치도구

java, class, js, jsp 등 패치파일을 쉽게 가져올 수 있는 프로그램을 작성했다.

이미 공개된 배포도구가 많기 때문에 이렇게 원시적인 프로그램을 사용할지는 의문이지만,

개인적으로 폐쇄망/내부망 환경에서 유용하게 쓰고 있다.

■ 다운로드

첨부파일 BBPatchHelper_181005.zip 을 다운받으면 된다.

실행파일은 bin 폴더 안에 있으며, 소스파일은 sources 폴더 안에 있다.

■ 이 프로그램의 목적

주로 java로 작성된 파일들을 쉽게 가져오기 위해 만든 프로그램이다.

복사금지 패턴이 있어서 실수로 훼손될 수 있는 민감한 확장자를 제외하고 원하는 파일만 가져올 수 있다.

[.java 대신 .class 가져오기] 에 체크하면, java 경로를 입력해도 class 파일을 가져온다.

단, 이클립스 등을 통해 class 가 생성되어 있어야 한다.

실제로 java 파일을 파싱해서 상단의 package 라인을 읽어들이고, 해당 위치의 class 파일을 가져오게 설계되어 있다.

■ 사용법

1. [대상 폴더] 란은 비우고, [대상 파일] 란에 여러 개의 파일 경로를 기입한다.

이후 COPY 버튼을 누르면 [결과 폴더] 위치에 파일이 복사된다.

2. [결과 폴더]는 존재하지 않아도 COPY할 때 자동으로 생성된다.

프로그램이 새로 실행될 때마다 [결과 폴더] 값은 존재하지 않는 새폴더명으로 새로고침된다.

3. 만약 [.java 대신 .class 가져오기] 에 체크가 되어 있다면 java 파일을 근거로 class 파일을 찾아 복사한다.

(컴파일을 대신 해주진 않는다. 이미 컴파일되어 있는 class를 찾아온다.)

4. [대상 폴더] 란에 특정 폴더 경로를 기입하고, [대상 파일] 란은 비운채 [COPY]하면, 해당 폴더가 통째로 복사된다.

이 때, [java 대신 class 가져오기]는 적용되지 않으나, [복사금지 패턴]은 적용된다.

한 마디로, 특정 확장자를 제외하고 폴더째로 복사해온다.

5. 기타

– 슬래시와 역슬래시는 둘 중 어느 것을 사용해도 괜찮다. (섞어쓰거나 2~3개 연속되어도 상관없다.)

– 실행파일에 동봉된 option.properties 파일을 수정해서 기본값을 변경할 수 있다.

등등…

P.S. 사실 회사 업무에 최적화시킨 툴이라 일반적으로 쓰기에는 그다지 유용하지 않을 수도 있다.

예를 들어 회사 업무상 파일 경로에는 무조건 workspaces 가 포함된다.

파일 경로에 (1) workspace (2) workspaces (3) git  (4) gits 폴더가 포함된 경우 [AUTO] 버튼이 유용하다.

아래와 같이 경로에 git 폴더가 포함되어 있는 경우, [AUTO] 버튼을 누르면 아래와 같은 효과가 있다.

이클립스 기본규칙에 따라 git 폴더(또는 workspaces 폴더) 밑의 2뎁스까지를 대상 폴더로 인지하고,

대상 파일들을 경로에 맞춰 정리해준다.

또한 [AUTO] 버튼은 중복도 제거해준다. (물론 어차피 [COPY]하면 중복은 무시된다.)

[Java] bat 파일로 java 실행 (java 실행파일 만들기)

[Java] bat 파일로 java 실행 (java 실행파일 만들기)

윈도우에서 java 프로그램을 실행할 수 있는 bat파일을 만들어본다.

우선 Runnable JAR 파일을 만들어야 한다.

1. 이클립스 상단 메뉴의 [File] – [Export] 를 클릭한다.

  

2. Export 창이 뜨면 [Java] – [Runnable JAR File] 을 선택하고 [Next] 를 클릭한다.

적당한 위치를 선택하면 실행 가능한 JAR 파일이 생성된다.

 

3. 아래와 같이 jar 파일이 위치한 곳에 bat 파일을 만들어준다.

bat 파일을 만드는 법은 메모장을 열고 [파일명.bat] 로 저장하면 된다.

 

4. 메모장으로 bat 파일을 열고 start java -jar ./파일명.jar 라고 입력한 후 저장한다.

예를 들어 아래와 같이 start java -jar ./BBTest.jar 라고 입력한다.

 

이 경우 bat 파일을 실행하면 아래와 같이 콘솔과 함께 자바 프로그램이 실행된다.

콘솔에는 System.out.println 등으로 찍은 문자열 등이 출력된다.

 

5. 이번에는 메모장으로 bat 파일을 열고 start javaw -jar ./파일명.jar 라고 입력한 후 저장한다.

예를 들어 아래와 같이 start javaw -jar ./BBTest.jar 라고 입력한다.

 

이 경우 아래와 같이 콘솔 프로그램 없이 자바 프로그램만 실행된다.

JFrame 등을 통해 윈도우 창을 구현한 경우 사용하면 좋다.

끝.

익스플로러에서 구글을 기본검색엔진으로 설정

익스플로러에서 구글을 기본검색엔진으로 설정

Internet Explorer 주소창에 검색어를 입력하면 자꾸 Bing으로 연결되곤 한다.

Bing은 잘 쓰지도 않는 검색엔진인데 참 불편하다.

본 포스트는 Internet Explorer에서 Google을 기본검색엔진으로 설정하는 방법을 소개한다.

(Internet Explorer 11 버전 기준이다.)

1. 익스플로러 상단 검색창의 [화살표] 를 클릭 – [추가] 버튼을 클릭한다.

이어서 [InternetExplorer 갤러리] 화면이 나타나면, 구글 로고가 붙어있는 [Google Search Suggestions] 하단의 [추가] 를 클릭한다.

 

2. 익스플로러 상단 [톱니바퀴] 를 클릭 – [추가 기능 관리(M)] 를 클릭한다.

 
 

 

 

3. [추가 기능 관리] 창이 뜰 것이다.

좌측의 [검색 공급자] 클릭 – [Google] 을 선택하고 – 하단의 [기본값으로 설정] 버튼 클릭한다.



  

 

4. 이제 Bing은 필요없으니, [Bing] 을 선택하고 하단의 [제거] 버튼을 클릭해서 제거하자.

 

 

 

5. 최종 적용된 모습

 

이제 주소창에 검색어를 입력하면 구글로 연결될 것이다.

크롬이나 익스플로러 기타 버전(11 버전이 아닌 10, 9, 8 버전)은 아래 [참고자료] 링크를 확인해서 따라하면 된다.

* 참고자료

http://support.google.com/websearch/answer/464?hl=ko 

BBDirDecompiler : 폴더용 자바 디컴파일러

BBDirDecompiler : 폴더용 자바 디컴파일러

폴더 기준으로 자바 class 파일들을 java 파일로 디컴파일 해주는 프로그램을 작성했다.

코어 부분인 디컴파일은 동봉된 jad 라는 프리웨어로 수행하므로, 특별히 대단한 프로그램은 아니다.

■ 다운로드

첨부파일 BBDirDecompiler_180926_02.zip 를 다운받으면 된다.

실행파일은 bin 폴더 안에 있으며, 소스파일은 sources 폴더 안에 있다.

■ 이 프로그램의 목적

서버에 있는 class 파일들과 로컬에 있는 class 파일들이 서로 다르다고 의심될 때 사용 가능하다.

각각의 폴더를 이 프로그램을 이용해서 java로 디컴파일 시킨 다음, WinMerge와 같은 프로그램으로 폴더별 diff 해서 둘의 차이를 알아낼 수 있다.

■ 사용법

1. 아래와 같이 자바 class파일들이 들어있는 폴더를 준비한다.

2. 프로그램을 실행한다. BBDirDecompiler_180926.bat 를 더블클릭하면 실행된다.

3. 상단 인풋박스에 자바 class 파일들이 들어있는 폴더 경로를 입력한다.

주의할 점은 파일 경로가 아닌 폴더 경로를 입력해야 한다는 점이다.

파일을 디컴파일 하고 싶다면 특정 폴더 안에 해당 파일을 넣고, 특정 폴더의 경로를 입력하자.

 

4. 우측 상단의 Decompile 버튼을 클릭한다.

 

5. 디컴파일이 모두 수행되면, 하단 콘솔영역에 결과 폴더가 표시된다.

결과 폴더는 최초 입력한 폴더 곁에 생성된다.

예를 들어 여기서는 최초 폴더가 C:\test\classes 이고, 결과 폴더는 C:\test\classes_20180926191216258 이다.

 

js 인풋박스 셀렉트에 대한 소고

js 인풋박스 셀렉트에 대한 소고

자바스크립트로 HTML요소, 예를 들면 인풋박스의 값을 가져올 때 document의 getElementById를 쓰거나 JQuery로 셀렉트하는게 일반적이다.

이를 테면 document.getElementById(“temp”).value; 또는 $(“#temp”).val(); 와 같이 말이다.

그런데 회사 옛날 코드를 보면 그렇지 않고 temp.value; 와 같은 코드가 많다. 여태 별 신경쓰지 않았는데 회사 동료가 구체적으로 물어봐서 테스트해봤다.

테스트 결과 인풋박스의 경우 [id].value 를 사용하면 해당 인풋박스를 찾아온다. 아마 브라우저 렌더링 과정에서 document객체에 인풋박스 아이디들을 하위요소로 넣어주는 것 같다.

문제는 익스플로러에서는 [id].value 와 [name].value 를 다 잘 가져오는데, 크롬은 id로만 가져올 수 있고 name은 가져오지 못한다. 브라우저마다 디펜던시가 있는 것으로, 파이어폭스와 오페라, 사파리도 각각 다를 것이라 추정된다.

이럴 것을 알고 과거부터 번거로워도 document.getElementById 를 고수해왔다. 게다가 해당 변수가 운 나쁘게 전역변수로 쓰이고 있을지도 모르지 않는가? 나는 고전적인 코드가 좋다. 브라우저 종류와 버전을 잘 타지 않는 범용 코드 말이다.

<엑셀VBA 입문 16강> VBA로 워크시트 함수 활용하기

<엑셀VBA 입문 16강> VBA로 워크시트 함수 활용하기

VBA에서 기존 엑셀 함수(워크시트 함수), 예를 들면 SUM, AVERAGE, SUMIF, SUMIFS, VLOOKUP을 사용할 수 없을까?

결론부터 밝히면 가능하다.

VBA로 기존 엑셀 함수를 쓰기 위해서는 <Application.WorksheetFunction.기존함수> 명령어를 사용하면 된다.

예를 들어 SUM 함수를 쓰고 싶다면 Application.WorksheetFunction.Sum

AVERAGE 함수를 쓰고 싶다면 Application.WorksheetFunction.Average 를 쓰면 된다.

아래 예제코드를 참고하자.

Sub 매크로1()

‘ 매크로1 매크로

‘ 바로 가기 키: Ctrl+k

    Set myRange = Worksheets(“Sheet1”).Range(“A1:C10”)

    Dim result

    ‘SUM함수 사용

    result = Application.WorksheetFunction.Sum(myRange)
    MsgBox (“합계 : ” & result)

    ‘AVERAGE함수 사용 

    result = Application.WorksheetFunction.Average(myRange)
    MsgBox (“평균 : ” & result)

End Sub

결과는 아래와 같이 나올 것이다.

이렇게 VBA에서 엑셀 함수는 사용할 수 있지만, 함수 내부의 내용은 아쉽게도 볼 수 없다. 

VBA에서 사용할 수 있는 엑셀 함수에 대한 설명은 마이크로소프트 문서에 잘 나와있다.

링크 : https://docs.microsoft.com/en-us/office/vba/api/excel.worksheetfunction

위 문서에 따르면 사용할 수 있는 함수 목록은 아래와 같다.

웬만큼 쓰는 함수는 다 들어있음을 알 수 있다.

AccrInt, AccrIntM, Acos, Acosh, Acot, Acoth, Aggregate, AmorDegrc, AmorLinc, And, Arabic, Asc, Asin, Asinh, Atan2, Atanh, AveDev, Average, AverageIf, AverageIfs, BahtText, Base, BesselI, BesselJ, BesselK, BesselY, Beta_Dist, Beta_Inv, BetaDist, BetaInv, Bin2Dec, Bin2Hex, Bin2Oct, Binom_Dist, Binom_Dist_Range, Binom_Inv, BinomDist, Bitand, Bitlshift, Bitor, Bitrshift, Bitxor, Ceiling, Ceiling_Math, Ceiling_Precise, ChiDist, ChiInv, ChiSq_Dist, ChiSq_Dist_RT, ChiSq_Inv, ChiSq_Inv_RT, ChiSq_Test, ChiTest, Choose, Clean, Combin, Combina, Complex, Confidence, Confidence_Norm, Confidence_T, Convert, Correl, Cosh, Cot, Coth, Count, CountA, CountBlank, CountIf, CountIfs, CoupDayBs, CoupDays, CoupDaysNc, CoupNcd, CoupNum, CoupPcd, Covar, Covariance_P, Covariance_S, CritBinom, Csc, Csch, CumIPmt, CumPrinc, DAverage, Days, Days360, Db, Dbcs, DCount, DCountA, Ddb, Dec2Bin, Dec2Hex, Dec2Oct, Decimal, Degrees, Delta, DevSq, DGet, Disc, DMax, DMin, Dollar, DollarDe, DollarFr, DProduct, DStDev, DStDevP, DSum, Duration, DVar, DVarP, EDate, Effect, EncodeURL, EoMonth, Erf, Erf_Precise, ErfC, ErfC_Precise, Even, Expon_Dist, ExponDist, F_Dist, F_Dist_RT, F_Inv, F_Inv_RT, F_Test, Fact, FactDouble, FDist, FilterXML, Find, FindB, FInv, Fisher, FisherInv, Fixed, Floor, Floor_Math, Floor_Precise, Forecast, Frequency, FTest, Fv, FVSchedule, Gamma, Gamma_Dist, Gamma_Inv, GammaDist, GammaInv, GammaLn, GammaLn_Precise, Gauss, Gcd, GeoMean, GeStep, Growth, HarMean, Hex2Bin, Hex2Dec, Hex2Oct, HLookup, HypGeom_Dist, HypGeomDist, IfError, IfNa, ImAbs, Imaginary, ImArgument, ImConjugate, ImCos, ImCosh, ImCot, ImCsc, ImCsch, ImDiv, ImExp, ImLn, ImLog10, ImLog2, ImPower, ImProduct, ImReal, ImSec, ImSech, ImSin, ImSinh, ImSqrt, ImSub, ImSum, ImTan, Index, Intercept, IntRate, Ipmt, Irr, IsErr, IsError, IsEven, IsFormula, IsLogical, IsNA, IsNonText, IsNumber, ISO_Ceiling, IsOdd, IsoWeekNum, Ispmt, IsText, Kurt, Large, Lcm, LinEst, Ln, Log, Log10, LogEst, LogInv, LogNorm_Dist, LogNorm_Inv, LogNormDist, Lookup, Match, Max, MDeterm, MDuration, Median, Min, MInverse, MIrr, MMult, Mode, Mode_Mult, Mode_Sngl, MRound, MultiNomial, Munit, NegBinom_Dist, NegBinomDist, NetworkDays, NetworkDays_Intl, Nominal, Norm_Dist, Norm_Inv, Norm_S_Dist, Norm_S_Inv, NormDist, NormInv, NormSDist, NormSInv, NPer, Npv, NumberValue, Oct2Bin, Oct2Dec, Oct2Hex, Odd, OddFPrice, OddFYield, OddLPrice, OddLYield, Or, PDuration, Pearson, Percentile, Percentile_Exc, Percentile_Inc, PercentRank, PercentRank_Exc, PercentRank_Inc, Permut, Permutationa, Phi, Phonetic, Pi, Pmt, Poisson, Poisson_Dist, Power, Ppmt, Price, PriceDisc, PriceMat, Prob, Product, Proper, Pv, Quartile, Quartile_Exc, Quartile_Inc, Quotient, Radians, RandBetween, Rank, Rank_Avg, Rank_Eq, Rate, Received, Replace, ReplaceB, Rept, Roman, Round, RoundDown, RoundUp, Rri, RSq, RTD, Search, SearchB, Sec, Sech, SeriesSum, Sinh, Skew, Skew_p, Sln, Slope, Small, SqrtPi, Standardize, StDev, StDev_P, StDev_S, StDevP, StEyx, Substitute, Subtotal, Sum, SumIf, SumIfs, SumProduct, SumSq, SumX2MY2, SumX2PY2, SumXMY2, Syd, T_Dist, T_Dist_2T, T_Dist_RT, T_Inv, T_Inv_2T, T_Test, Tanh, TBillEq, TBillPrice, TBillYield, TDist, Text, TInv, Transpose, Trend, Trim, TrimMean, TTest, Unichar, Unicode, USDollar, Var, Var_P, Var_S, VarP, Vdb, VLookup, WebService, Weekday, WeekNum, Weibull, Weibull_Dist, WorkDay, WorkDay_Intl, Xirr, Xnpv, Xor, YearFrac, YieldDisc, YieldMat, Z_Test, ZTest, Forecast_ETS, Forecast_ETS_ConfInt, Forecast_ETS_Seasonality, Forecast_ETS_STAT, Forecast_Linear

이어지는 글 <엑셀VBA 입문 17강> 변수를 같은 이름으로 사용하면 섞일까? (지역변수와 전역변수) :

https://blog.naver.com/bb_/221417490850

이어지는 글 <엑셀VBA 입문 18강> indexOf 함수의 확장 https://blog.naver.com/bb_/221650929713

엑셀VBA 익스플로러(윈도우) 포커싱하는 법

엑셀VBA 익스플로러(윈도우) 포커싱하는 법

 

VBA로 뭔가 만들려고 할 때 엑셀만 제어해서 될 일이 아니라 다른 창이나 폴더를 제어해야 하는 경우가 많다. 지난 번 썼던 <엑셀VBA 오토마우스 만들기>를 참고하여 단순히 Alt+tab 을 누르도록 키보드 후킹해도 좋지만, 이번 내용이 좀 더 고급기술이다.

 

먼저 캡션과 클래스에 대해 간단히 알아보고 시작한다.

1. 띄우고 싶은 윈도우(창)에 대해 이해하기

프로그램에는 클래스명(Class)과 캡션(Caption)이라는 개념이 있다. 둘 중 하나만 알아내면 윈도우를 찾을 수 있는데, 우리는 캡션을 쓸 것이다.

 

첫번째로 클래스명은 변하지 않는 프로그램 고유의 것이다. 알아내는 법은 spy++같은 프로그램으로 ctrl+f 하여 해당 프로그램을 드래그하면 된다. 그러나 우리는 클래스명보다는 캡션을 쓸 것이니 자세히는 몰라도 된다. (Spy++는 보통 C++같은 언어를 설치할 때 동봉된다)

 

두번째로 캡션은, 해당 프로그램 윈도우 제목 부분에 쓰여있는 글이다. 이건 경우에 따라 변화하므로 주의해야 한다. 아래부터는 예를 통해 쉽게 이해해보자.

 

1-1. 폴더로 이해하기


 

“수당”이라는 폴더를 켠 상황이다. 보시다시피 윈도우 상단부에 “수당”이라고 적혀있다. 이게 캡션이다.

 

1-2. 메모장으로 이해하기


 

메모장을 켠 상황이다. 메모장의 클래스네임은 항상 “Notepad”이지만, 캡션은 어떻게 저장하냐에 따라 바뀔 수도 있다. 여기서의 캡션은 윈도우 최상단부에 쓰여있듯 “ㅇ.txt – 메모장”이다. 여기서 띄어쓰기라거나 대소문자만 바뀌어도 VBA는 인식을 못한다. 주의하자.

 

지금 이 파일을 ㅅ.txt로 바꿔 저장한다면 캡션도 바뀐다. 이렇듯 파일 이름에 따라 캡션은 바뀔 수 있다. 엑셀도 마찬가지다. 다시 말해 캡션으로 포커스하는 소스를 만들어두면, 캡션이 바뀜에 따라 포커스가 안될 수 있으므로 주의해야 한다.

 

(참고) 윈도우 핸들시 유용한 클래스 네임

참고삼아 유용한 클래스 네임은 다음과 같다. 파워포인트2007 “PP12FrameClass”, 파워포인트2003 “PP11FrameClass”, 파워포인트2002 (PPT XP버젼) “PP10FrameClass”, 파워포인트2000 “PP9FrameClass”, 파워포인트97 “PP97FrameClass”, 엑셀 “XLMAIN”, 워드 “OpusApp”, 메모장 “Notepad”

 

1-3. 익스플로러로 이해하기


 

마지막으로 익스플로러를 통해 이해해보자. 캡션은 “네이버 :: 나의 경쟁력, 네이버 – Windows Internet Explorer” 가 되겠다. 여기서 한 글자만 틀리거나 빠져도 인식하지 못한다.

 

2. VBA소스


2-1. 각종 선언들

‘//원하는 윈도우 핸들을 찾기 위한 선언

Private Declare Function FindWindow Lib “user32” Alias “FindWindowA” (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

 

‘//원하는 윈도우를 포커스하기 위한 선언

Public Declare Function SetForegroundWindow Lib “user32” (ByVal hwnd As Long) As Long

 

2-2. 윈도우 포커싱 소스(폴더나 익스플로러)

‘//ihwnd = FindWindow(클래스명, 캡션) 으로 핸들값을 받아올 수 있다. 이 때 클래스명과 캡션 둘 중 하나만 써넣고 나머지는 Null 처리해도 된다. 창을 못 찾았을 경우 0 을 돌려준다.

 

Dim ihwnd As Long
ihwnd = FindWindow(vbNullString, “수당”)

If ihwnd = 0 Then
    MsgBox “현재 해당 윈도우를 찾을 수 없습니다”

Else
    Call SetForegroundWindow(ihwnd)
End If

 

‘//수당이라는 폴더가 켜져있지 않으면 메세지를 나타내고, 켜져있으면 해당 윈도우를 포커싱한다. 익스플로러나 메모장도 똑같은 원리로 응용하면 되겠다.

 

2-3. 메모장 포커싱 소스

Dim ihWnd As Long
ihWnd = FindWindow(“Notepad”, vbNullString)
If ihWnd = 0 Then
    Shell “C:\Windows\Notepad.exe”

Else
    MsgBox “메모장이 켜져있습니다”
    Call SetForegroundWindow(ihWnd)
End If
   

‘//메모장이 켜져있지 않으면 메모장을 실행, 그렇지 않으면 메모장을 포커싱하는 소스다.

 

2-4. 현재 작업중인 엑셀로 포커싱

‘//다른 창에서 작업하다가 다시 엑셀로 돌아갈 때 유용하다.

Dim ihwnd As Long
ihwnd = FindWindow(vbNullString, Application.Caption)

If ihwnd = 0 Then
    MsgBox “현재 엑셀을 찾을 수 없습니다”

Else
    Call SetForegroundWindow(ihwnd)
End If

‘//특히 엑셀의 클래스명 XLMAIN을 쓰기보단 위의 소스를 쓰는 게 개인적으로 더 좋았다.

 

2-5. 윈도우 제어하기

원래는 클래스명을 이용하여 자식클래스 뭐시기 하면서 제어하는 게 정석. 그러나 오토마우스 혹은 오토키보드를 이용하는게 접근하기 쉽다. 지난 번 썼던 <엑셀VBA 오토마우스 만들기>를 참고하여 제어해보자.
 

엑셀VBA 현재 파일명 추출하기

엑셀VBA 현재 파일명 추출하기

위와 같은 상황에서 사용했을 시 아래와 같은 효과를 얻는 소스를 공개한다.

 

 

(1) 기본 소스

a = Application.Caption
MsgBox a ‘(1)번째 메시지박스

 

a = Replace(a, ” “, “a”, , 2) ‘변수a의 공백 2개를 문자”a”로 치환함
MsgBox a ‘(2)번째 메시지박스

 

For i = 1 To Len(a)
    If Mid(a, i, 1) = ” ” Then
    a = Mid(a, i + 1, Len(a) – i)

    Exit For
    End If
Next i

MsgBox a ‘(3)번째 메시지박스

 

(2) 좀 더 최적화된 소스 (… 별 차이는 없다.)

a = Replace(Application.Caption, ” “, “a”, , 2) ‘전체캡션의 공백 2개를 문자”a”로 치환함

For i = 1 To Len(a)
    If Mid(a, i, 1) = ” ” Then
    a = Mid(a, i + 1, Len(a) – i)
    Exit For
    End If
Next i

MsgBox a ‘메시지박스로 확인

 

(3) 뒤늦게 발견한 가장 좋은 소스

MsgBox ActiveWindow.Caption

엑셀VBA 컴파일 오류, Declare 문을 검토하고 업데이트 한 다음 PtrSfae 특성으로 표시하십시오

엑셀VBA 컴파일 오류, Declare 문을 검토하고 업데이트 한 다음 PtrSfae 특성으로 표시하십시오

엑셀VBA에서, <컴파일 오류입니다. 이 프로젝트의 코드를 업데이트해야 64비트 시스템에서 사용할 수 있습니다. Declare 문을 검토하고 업데이트 한 다음 PtrSfae 특성으로 표시하십시오.>

 

라는 문구가 아래처럼 뜨는 경우가 있다.

 



 

 이 때 “Declare”를 “Declare PtrSafe”로 변경하면 해결된다.

 

ex)’ Public Declare Function SetForegroundWindow Lib “user32” (ByVal hwnd As Long) As Long’ 을 ‘Public Declare PtrSafe Function SetForegroundWindow Lib “user32” (ByVal hwnd As Long) As Long’ 로 바꿈

 

엑셀VBA 인터넷익스플로러 로딩중인지 확인하는 법

엑셀VBA 인터넷익스플로러 로딩중인지 확인하는 법

 

아래 소스를 붙여넣고 Macro1을 실행할 때 내용은 다음과 같다. 먼저 인터넷 창을 띄우고, 네이버에 접속한 후 로딩이 되는대로 ‘a’를 검색, 다시 로딩이 끝나는대로 ‘b’를 검색, 다시 로딩이 완료되면 ‘c’를 검색한다. 이 모든것이 자동으로 이뤄지는 것이다.

 

익스플로러가 로딩 중인지 아닌지를 판단하는 것은 상당히 중요한 이슈로, 로딩이 몇 초에 완료될지 몰라서 무작정 넉넉히 sleep을 걸어야하는 낭비를 줄일 수 있다.

 

‘//원하는 윈도우를 포커스하기 위한 선언 

Private Declare Function SetForegroundWindow Lib “user32” (ByVal hwnd As Long) As Long
‘//윈도우 핸들을 찾기 위한 선언
Private Declare Function FindWindow Lib “user32” Alias “FindWindowA” (ByVal lpClassName As String, ByVal lpWindowName As String) As Long

 

‘//일정시간을 대기하는 함수 *Sleep(n) = n/1000초 동안 대기
Private Declare Sub Sleep Lib “kernel32” (ByVal dwMilliseconds As Long)

 

‘//키보드 상태확인을 위한 선언하기
Private Declare Function GetAsyncKeyState Lib “user32” (ByVal vKey As Long) As Integer
‘//키입력을 위한 선언하기
Const KEYEVENTF_EXTENDKEY = &H1
Const KEYEVENTF_KEYUP = &H2
Private Declare Sub keybd_event Lib “user32.dll” (ByVal bVk As Byte, ByVal bScan As Byte, ByVal dwFlags As Long, ByVal dwExtraInfo As Long)

 

‘//인터넷 익스플로러 선언

‘//도구-참조-Microsoft Internet controls​ (또는 C:\windows\system32\shdocvw.dll)

Dim IE As New InternetExplorer
‘//기억할 윈도우 핸들
Dim IEhWnd

 

Sub Macro1()

‘//익스플로러를 열어 지정해둔 주소로 접속한다
IE.Visible = True
IE.Navigate “naver.com”
IEhWnd = IE.hwnd ‘//새로 열린 윈도우 핸들을 기억한다

Call LoadWait ‘//로딩중 대기

Call goIE ‘//로딩 끝나면, 익스플로러를 열고(goIE)

Call KeyA: Call Enter: Call LoadWait ‘//”A”를 검색하고 로딩대기

Call KeyB: Call Enter: Call LoadWait ‘//”B”를 검색하고 로딩대기

Call KeyC: Call Enter: Call LoadWait ‘//”C”를 검색하고 로딩대기

‘Call goExcel ‘//엑셀로 돌아오기
End Sub

Function LoadWait()
‘//로딩하는 동안 대기한다

If IE.hwnd <> 0 Then ‘//익스플로러가 켜져있을 때만
Do While IE.Busy Or IE.ReadyState <> READYSTATE_COMPLETE ‘//익스플로러가 로딩 중이면 대기한다
DoEvents
Loop
Sleep (1000)

Else
MsgBox “익스플로러가 켜져있지 않습니다”
End
End If
End Function

 

Function goIE()
‘//기억해둔 윈도우 핸들을 불러낸다.
   ‘// 참고로 “IEhWnd = FindWindow(“IEFrame”, vbNullString)”라는
   ‘// 1줄을 추가하면 익스플로러를 찾아서 불러낸다
Call SetForegroundWindow(IEhWnd)
Call Sleep(1000) ‘//1초 쉰다
End Function

Function goExcel()
‘//다른 창에서 작업하다가 다시 엑셀로 돌아갈 때 유용하다

Dim ihWnd As Long
ihWnd = FindWindow(vbNullString, Application.Caption)

If ihWnd = 0 Then
    MsgBox “현재 엑셀을 찾을 수 없습니다”
Else
    Call SetForegroundWindow(ihWnd)
End If
End Function

 

Function Enter()
keybd_event 13, 0, 0, 0 ‘//엔터
keybd_event 13, 0, KEYEVENTF_KEYUP, 0
Call Sleep(100) ‘//0.1초 쉰다

DoEvents
End Function

 

Function KeyA()
keybd_event 65, 0, 0, 0 ‘//A키
keybd_event 65, 0, KEYEVENTF_KEYUP, 0
Call Sleep(100) ‘//0.1초 쉰다

DoEvents
End Function

 

Function KeyB()
keybd_event 66, 0, 0, 0 ‘//B키
keybd_event 66, 0, KEYEVENTF_KEYUP, 0
Call Sleep(100) ‘//0.1초 쉰다
DoEvents
End Function

 

Function KeyC()
keybd_event 67, 0, 0, 0 ‘C키
keybd_event 67, 0, KEYEVENTF_KEYUP, 0
Call Sleep(100) ‘//0.1초 쉰다

DoEvents
End Function

[Java] File객체의 listFiles() 정렬기준에 대한 메모

[Java] File객체의 listFiles() 정렬기준에 대한 메모

자바의 File객체에는 listFiles()라는 메서드가 있다. File객체가 디렉토리일 경우 디렉토리 내부의 파일들을 배열로 만들어 File[] 형태로 리턴해준다.

업무상 어떤 일배치 프로그램이 있다. 앞서 설명한 listFiles() 메서드를 사용해 리턴받은 File배열을 for문 돌려 처리하는 프로그램이다. 이 프로그램은 요건상 꼭 파일명 순으로 처리가 되어야 한다. 그런데 이번에 파일명 순으로 for문이 돌지 않는다는 버그가 리포트되었다.

여러가지로 테스트를 시도해봤지만 listFiles()는 파일명 오름차순의 File객체 배열을 잘 만들어주고 있었다. 윈도우 상에서 이런저런 테스트를 해보다가 결국 리눅스 서버에 올려 테스트를 해보았는데, 리눅스 상에서는 날짜순으로 배열을 만들어 리턴하는게 아닌가.

자바 버전의 차이인지 운영체제의 차이인지는 모르겠지만 앞으로 listFiles()를 사용할 때는 명시적으로 원하는 기준을 정해 정렬시키고 작업해야겠다는 생각이 들었다. 배포할 때마다 자바 및 운영체제 버전을 일일히 체크하기보다, 마음 편하게 명시적으로 정렬해주는게 낫다는 생각이다.

C++ 문자열 결합, 문자열을 숫자로 변환, 숫자를 문자열로 변환

C++ 문자열 결합, 문자열을 숫자로 변환, 숫자를 문자열로 변환

너무 오랜만에 C++을 해보니 문자열 결합부터가 헷갈려서 간단히 정리해둔다.

먼저 문자열은 아래와 같이 선언한다.

std::string str1 = “Hello World.”;

C++에서 문자열 출력은 아래와 같이 한다.

// 아래 코드는 cout << “Hello World.” << endl; 와 같음

std::string str1 = “Hello World.”;

cout << str1.c_str() << endl;

보다시피 std::string 객체에 멤버함수로 c_str() 이 있다.

c_str() 함수는 문자열을 char* 형태로 리턴해준다.

C++을 잘 모르는 나이지만, 이 함수가 상당히 고마운 존재라는 것은 알 수 있다.

참고로 cocos2d-x 라이브러리에는 log 라는 함수가 있다.

log 는 콘솔에 문자열을 출력해준다.

이 때, 문자열은 char* 로 넘겨야 한다. 역시 c_str() 함수를 사용해야 한다는 얘기다.

숫자 출력

int num = 10;
log(“num : %d”, num);

숫자 더하기 후 출력

int num1 = 10;
int num2 = 20;
int num3 = num1 + num2;
log(“num3 : %d”, num3);

문자열 출력 1 (char* 사용)

char* str = “10”;
log(“str : %s”, str);

문자열 출력 2 (std::string 사용)

std::string str1 = “10”;
log(“str1 : %s”, str1.c_str());

문자열 결합 후 출력

std::string str2 = “10”;
std::string str3 = “20”;
std::string str4 = str2 + str3;
log(“str4 : %s”, str4.c_str());

숫자를 문자열로 변환

int num10 = 10;
std::string str10 = std::to_string(num10);

log(“str10 : %s”, str10.c_str());

문자열을 숫자로 변환 1 (char* to int)

char* str20 = “10”;
int num20 = std::atoi(str20);
log(“num20 : %d”, num20);

문자열을 숫자로 변환 2 (std::string to int)

std::string str21 = “10”;
int num21 = std::stoi(str21);
log(“num21 : %d”, num21);

비브리아 웹버전

비브리아 웹버전

안녕하세요, 흑곰입니다.

똥똥배님의 게임 <비브리아>를 웹버전으로 리메이크 해보았습니다.

개인적으로 리메이크를 해도 되는지 궁금해서

좀 뒤늦은 타이밍이었지만 똥똥배님 블로그에 안부글을 남겨 허락을 받았습니다.

그래픽은 타일/캐릭터 모두 다시 그렸습니다.

게임 룰만 똥똥배님 것을 가져온 것이죠.

바로가기 => http://ddoc.kr:18080/game/vivria/room

<게임하는 법>

1. 위 주소로 접속

2. 방장은 [방 개설] 버튼으로 방 생성

3. 참가자는 목록에서 해당 방 클릭하여 입장

4. 참가자는 상단의 [게임준비] 버튼 클릭으로 준비 상태 설정

5. 방장은 상단의 [게임시작] 버튼 클릭으로 게임시작

<인원>

4인까지 게이머로 참가 가능합니다.

(1) 1:1

(2) 1:1:1

(3) 1:1:1:1 이 지원됩니다.

관전자 숫자에는 제한이 없습니다.

<규칙> (똥똥배님 쓰심)

* 목표는 상대방 왕 비브리아를 잡는 것.
– 비브리아는 1~10까지의 크기를 가지는데 아군끼리 합체할 수 있다.
– 적의 비브리아를 공격하면 숫자가 같거나 크면 흡수하고, 작으면 흡수 당한다.
– 비브리아 크기가 10을 초과하면 배가 터져 죽는다.
– 비브리아 크기가 1~3이면 3칸 이동, 4~6은 2칸이동, 7~9는 1칸 이동, 10과 왕은 이동 불가.
– 크기가 10인 비브리아나 왕비브리아는 번식을 할 수 있는데,
  빈칸에 번식하면 크기가 1인 비브리아가 생기고,
  아군의 자리에 하면 아군의 크기가 1 커지고,
  적이 있는 곳에 번식을 하면 무조건 아군이 되고, 적의 크기에서 1을 더한 크기가 된다.

* 기존 비브리아와 다른 점 :

경기중 선수가 나가거나 패배시, 비브리아는 소멸되지 않고 남습니다.

남은 선수들은 시체를 활용한 플레이가 가능합니다.

[Spring4] 스프링 웹소켓(WebSocket) 예제

[Spring4] 스프링 웹소켓(WebSocket) 예제

스프링 웹소켓 예제를 구현해보았다.

검색해보니 구현방법이 무척 많았는데, 최대한 쉽게 구현된 형태를 참고하였다.

명월일지라는 블로그의 http://nowonbun.tistory.com/285 라는 포스트를 주로 참고하였다.

간단하게 다중 채팅방을 구현해보았다.

1. 스프링 웹 프로젝트 생성

먼저 스프링 웹 프로젝트가 필요하다.

기존에 있으면 건너뛰어도 된다. 기존에 없으면 아래 포스트를 참고해서 만들면 된다.

=> [Spring4] STS에서 스프링 웹 프로젝트 생성 (https://blog.naver.com/bb_/221339454799)

2. pom.xml 수정

WebSocket Server API 라는 라이브러리가 필요하다.

pom.xml 에 다음 코드를 추가하였다.

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
    <scope>provided</scope>
</dependency>

따로 다운받고 싶으면 MVN Repository를 방문하면 된다. https://mvnrepository.com/artifact/javax.websocket/javax.websocket-api 에서 1.1 버전 다운받으면 된다.

3. Websocket.java 파일 작성

적당한 위치에 Websocket.java 파일을 만들고 아래처럼 코드를 작성한다.

// 패키지 위치는 자유롭게 결정

package com.bb.vivria.socket;

import java.util.ArrayList;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint(“/websocket”)
public class Websocket {

    /**
     * 웹소켓 세션을 담는 ArrayList
     */

    private static ArrayList<Session> sessionList = new ArrayList<Session>();

    
    /**
     * 웹소켓 사용자 연결 성립하는 경우 호출
     */

    @OnOpen
    public void handleOpen(Session session) {
        if (session != null) {
            String sessionId = session.getId();
            
            System.out.println(“client is connected. sessionId == [“ + sessionId + “]”);
            sessionList.add(session);
            
            // 웹소켓 연결 성립되어 있는 모든 사용자에게 메시지 전송
            sendMessageToAll(“***** [USER-“ + sessionId + “] is connected. *****”);
        }
    }
    

    /**
     * 웹소켓 메시지(From Client) 수신하는 경우 호출
     */

    @OnMessage
    public String handleMessage(String message, Session session) {
        if (session != null) {
            String sessionId = session.getId();
            System.out.println(“message is arrived. sessionId == [“ + sessionId + “] / message == [“ + message + “]”);

            // 웹소켓 연결 성립되어 있는 모든 사용자에게 메시지 전송
            sendMessageToAll(“[USER-“ + sessionId + “] “ + message);
        }

        return null;
    }
    

    /**
     * 웹소켓 사용자 연결 해제하는 경우 호출
     */

    @OnClose
    public void handleClose(Session session) {
        if (session != null) {
            String sessionId = session.getId();
            System.out.println(“client is disconnected. sessionId == [“ + sessionId + “]”);
            
            // 웹소켓 연결 성립되어 있는 모든 사용자에게 메시지 전송
            sendMessageToAll(“***** [USER-“ + sessionId + “] is disconnected. *****”);
        }
    }

    
    /**
     * 웹소켓 에러 발생하는 경우 호출
     */

    @OnError
    public void handleError(Throwable t) {
        t.printStackTrace();
    }
    
    
    /**
     * 웹소켓 연결 성립되어 있는 모든 사용자에게 메시지 전송
     */

    private boolean sendMessageToAll(String message) {
        if (sessionList == null) {
            return false;
        }

        int sessionCount = sessionList.size();
        if (sessionCount < 1) {
            return false;
        }

        Session singleSession = null;

        for (int i = 0; i < sessionCount; i++) {
            singleSession = sessionList.get(i);
            if (singleSession == null) {
                continue;
            }

            if (!singleSession.isOpen()) {
                continue;
            }

            sessionList.get(i).getAsyncRemote().sendText(message);
        }

        return true;
    }
}

 

4. test.jsp 파일 작성

적당한 위치에 test.jsp 파일을 만들고 아래처럼 코드를 작성한다.

여기서는 webapp/WEB-INF/chat/test.jsp 에 위치한다고 가정한다.

<%@ page contentType=“text/html; charset=utf-8” %>
<!DOCTYPE html>
<html>
<head>
<meta http-equiv=“Content-Type” content=“text/html; charset=UTF-8”>
<title>웹소켓 테스트 페이지</title>
<script type=“text/javascript”>
var g_webSocket = null;
window.onload = function() {
    g_webSocket = new WebSocket(“ws://localhost:8080/websocket”);
    
    
    /**
     * 웹소켓 사용자 연결 성립하는 경우 호출
     */

    g_webSocket.onopen = function(message) {
        addLineToChatBox(“Server is connected.”);
    };
    
    
    /**
     * 웹소켓 메시지(From Server) 수신하는 경우 호출
     */

    g_webSocket.onmessage = function(message) {
        addLineToChatBox(message.data);
    };

    /**
     * 웹소켓 사용자 연결 해제하는 경우 호출
     */

    g_webSocket.onclose = function(message) {
        addLineToChatBox(“Server is disconnected.”);
    };

    /**
     * 웹소켓 에러 발생하는 경우 호출
     */

    g_webSocket.onerror = function(message) {
        addLineToChatBox(“Error!”);
    };
}

/**
* 채팅 박스영역에 내용 한 줄 추가
*/

function addLineToChatBox(_line) {
    if (_line == null) {
        _line = “”;
    }
    
    var chatBoxArea = document.getElementById(“chatBoxArea”);
    chatBoxArea.value += _line + “\n”;
    chatBoxArea.scrollTop = chatBoxArea.scrollHeight;    
}

/**
* Send 버튼 클릭하는 경우 호출 (서버로 메시지 전송)
*/

function sendButton_onclick() {
    var inputMsgBox = document.getElementById(“inputMsgBox”);
    if (inputMsgBox == null || inputMsgBox.value == null || inputMsgBox.value.length == 0) {
        return false;
    }
    
    var chatBoxArea = document.getElementById(“chatBoxArea”);
    
    if (g_webSocket == null || g_webSocket.readyState == 3) {
        chatBoxArea.value += “Server is disconnected.\n”;
        return false;
    }
    
    // 서버로 메시지 전송
    g_webSocket.send(inputMsgBox.value);
    inputMsgBox.value = “”;
    inputMsgBox.focus();
    
    return true;
}

/**
* Disconnect 버튼 클릭하는 경우 호출
*/

function disconnectButton_onclick() {
    if (g_webSocket != null) {
        g_webSocket.close();    
    }
}

/**
* inputMsgBox 키입력하는 경우 호출
*/

function inputMsgBox_onkeypress() {
    if (event == null) {
        return false;
    }
    
    // 엔터키 누를 경우 서버로 메시지 전송
    var keyCode = event.keyCode || event.which;
    if (keyCode == 13) {
        sendButton_onclick();
    }
}
</script>
</head>
<body>
    <input id=“inputMsgBox” style=“width: 250px;” type=“text” onkeypress=“inputMsgBox_onkeypress()”>
    <input id=“sendButton” value=“Send” type=“button” onclick=“sendButton_onclick()”>
    <input id=“disconnectButton” value=“Disconnect” type=“button” onclick=“disconnectButton_onclick()”>
    <br/>
    <textarea id=“chatBoxArea” style=“width: 100%;” rows=“10” cols=“50” readonly=“readonly”></textarea>
</body>
</html>

5. ChatController.java 파일 작성

도메인:포트/chat 으로 접속하면 test.jsp 에 도달할 수 있도록 ChatController.java 파일을 작성한다.

패키지 위치는 자유롭게 결정하면 된다.

예를 들어 localhost:8080/chat 으로 접속했을 때 test.jsp 가 뜨면 성공이다.

// 패키지 위치는 자유롭게 결정

package com.bb.vivria.controller;

import java.util.Locale;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
public class ChatController {
    @RequestMapping(value = “/chat”, method = RequestMethod.GET)
    public String home(Locale locale, Model model) {
        
        return “chat/test”;
    }
}

5. 테스트

브라우저 여러 개를 띄워, 도메인:포트/chat (예를 들어 localhost:8080/chat) 에 접속한 후 채팅을 해보자.

잘되면 성공이다.

이상 스프링 웹소켓(WebSocket) 예제를 마친다.

기관장님(본격단순슈팅게임)

기관장님(본격단순슈팅게임)

본격단순슈팅게임 <기관장님>입니다.

 

HTML로만 슈팅 게임을 한 번 만들어보자 라는 컨셉으로,

그리고 8월 31일 까지 출품인 똥똥배 대회를 홍보해보자는 취지로 만들었습니다.

 

HTML로만 만들다보니까 다소 느립니다.

느린 분은 크롬에서 돌리거나, 안하면(…)됩니다.

 

게임시작 링크1 -> http://hondoom.com/game/captain/index.html

게임시작 링크2 -> http://ddoc.kr/game/captain/index.html 

* 2016.07.04 제작하였고 백업차원에서 업로드.

[Spring4] STS에서 스프링 웹 프로젝트 생성

[Spring4] STS에서 스프링 웹 프로젝트 생성

0. 기본적인 개발환경 세팅

스프링 웹 프로젝트를 생성하기 이전 기본적인 개발환경 세팅을 반드시 해야 한다.

0-1. JAVA JDK 7 설치

0-2. 메이븐 설치

0-3. JAVA 환경변수 설정 / 메이븐 환경변수 설정


자세한 내용은 [Spring4] 스프링 개발환경 세팅 (https://blog.naver.com/bb_/221268887576) 에서 볼 수 있다.

1. STS 다운로드 / 압축해제

 

http://spring.io/tools 에 접속한다. [DOWNLOAD STS] 를 클릭하여 STS를 다운로드하고 특정 위치에 압축을 푼다.

현재 시점 STS버전은 3.9.5 이다. (spring-tool-suite-3.9.5.RELEASE-e4.8.0-win32-x86_64.zip)


 

zip파일의 압축을 풀어서 C:\coding\sts-bundle 폴더에 위치시켰다.

2. STS 실행

 

STS 바로가기 파일을 실행한다. (실제 위치는 C:\coding\sts-bundle\sts-3.9.5.RELEASE\STS.exe)

원하는 위치에 프로젝트를 생성하면 된다.

여기서는 프로젝트명을 TempProject 로 했다.



3. Spring Legacy Project 생성

 

STS 상단 메뉴의 [File] – [New] – [Spring Legacy Project] 를 선택해서 새 프로젝트를 생성한다.

 

[New Spring Legacy Project] 윈도우가 뜰 것이다. 프로젝트 이름은 TempProject 로 하고, Templates 항목은 Spring MVC Project 를 선택, [Next] 버튼을 클릭한다.

탑 레벨 패키지 명은 [com.bb.temp] 로 했다.

패키지 명명법은 [com.회사명.프로젝트명] 으로 하면 된다.

개인 개발자일 경우는 [com.개인닉네임.프로젝트명]으로 하면 된다.

위와 같이 새 프로젝트가 생성되었다.

 

4. 웹/와스서버 세팅 (tomcat)


 

STS 상단 메뉴의 [Window] – [Preferences] 를 눌러서 Preferences 윈도우를 띄운다.

Preferences 윈도우 좌측 메뉴의 [Server] – [Runtime Environments] 항목을 선택한다.

[Add] 버튼을 누르고 [Apache Tomcat v7.0] 을 선택하고 [Next] 버튼을 누른다.

물론 여기서 7.0 이 아닌 Tomcat 8.0 버전을 사용해도 괜찮다.

tomcat7 디렉토리의 위치를 입력하고 [Finish] 버튼을 클릭한다.

개인적으로 tomcat7 디렉토리의 위치는 C:\devtool\apache-tomcat-7.0.88 이다.

tomcat7이 없다면 다운로드 받으면 된다.

4-1. tomcat 7 다운로드 방법


tomcat7이 없을 경우, 위 그림과 같이 https://tomcat.apache.org 로 접속한다.

좌측 메뉴 Download의 [Tomcat 7]을 클릭하면 다운로드 페이지가 나온다.

zip 파일을 다운받아서 원하는 위치에 압축 해제해두면 설치가 끝난 것이다.

5. 웹/와스서버 기동 (tomcat 기동)

STS 좌측 패키지 탐색기 영역의 프로젝트 폴더(여기서는 TempProject 폴더) 위에서 마우스우클릭 후,

[Debug As] – [Debug on Server] 를 클릭한다.

물론 [Run As] – [Run on Server] 를 클릭해도 된다.

Debug on Server 는 디버그 모드로 프로젝트를 실행하고, Run on Server 는 일반 모드로 프로젝트를 실행한다.

Debug On Server 윈도우가 뜨면 [Next] – [Finish]를 차례로 눌러 톰캣 서버를 기동한다.

localhost:8080/temp 로 접속하면 Hello world! 가 표시된다.

 

6. 기타

6-1. 접속경로 변경

localhost:8080/temp 이 아니라 localhost:8080 로 접속하고 싶다면, server.xml 을 수정하면 된다.

server.xml 의

<Context docBase=”TempProject” path=”/temp” reloadable=”true” source=”org.eclipse.jst.jee.server:TempProject”/></Host>

<Context docBase=”TempProject” path=”/” reloadable=”true” source=”org.eclipse.jst.jee.server:TempProject”/></Host>

로 내용 수정한다.

이어서

<Connector connectionTimeout=”20000″ port=”8080″ protocol=”HTTP/1.1″ redirectPort=”8443″/>

<Connector connectionTimeout=”20000″ port=”80″ protocol=”HTTP/1.1″ redirectPort=”8443″/>

로 내용 수정한다.

6-2. 한글 인코딩 설정 (UTF-8 Encoding)

한글이 온전히 표시되게 하려면 UTF-8로 인코딩해야 한다.

6-2-1. 톰캣 한글 인코딩 설정

server.xml 의

<Connector connectionTimeout=”20000″ port=”80″ protocol=”HTTP/1.1″ redirectPort=”8443″/>

<Connector connectionTimeout=”20000″ port=”80″ protocol=”HTTP/1.1″ redirectPort=”8443″ URIEncoding=”UTF-8″/>

로 내용 수정한다.

이어서​

<Connector port=”8009″ protocol=”AJP/1.3″ redirectPort=”8443″/>

<Connector port=”8009″ protocol=”AJP/1.3″ redirectPort=”8443″ URIEncoding=”UTF-8″/>

로 내용 수정한다.

6-2-2. 프로젝트 한글 인코딩 설정


STS 좌측 패키지 탐색기 영역의 프로젝트 폴더(여기서는 TempProject 폴더) 위에서 마우스우클릭 후, [Properties] 선택한다.

Properties 윈도우가 뜨면 좌측 메뉴의 [Resource] 를 선택하고, Text file encoding 항목을 Other : UTF-8 로 입력하고, [Apply and Close] 클릭한다.

6-2-3. jsp파일 한글 인코딩 설정

jsp 파일 상단에 <%@ page contentType=”text/html; charset=utf-8″ %> 를 추가한다.

7. 결과


 STS에서 스프링 웹 프로젝트 생성이 완료되었다.

[JAVA] 순열(Permutation)하는 경우의 수

[JAVA] 순열(Permutation)하는 경우의 수

모 기업 코딩테스트를 치른후 순열과 조합에 취약하다는 것을 깨닫고 실습을 위해 코드 작성.

(사실 순열조합 뿐만이 아니라 모든 알고리즘 문제에 취약)

int 배열과 선택 개수를 파라미터로 넘기면 모든 순열(Permutation)하는 경우의 수를 가져오는 메서드 getPermutation을 작성했다.

(참고로 조합은 순서가 없는 경우의 수. 순열은 순서가 있는 경우의 수)

순열은 조합이 선행되어야 하므로, getCombination 메서드도 동봉되어 있다.

예를 들어 int 배열은 {1, 2, 3, 4, 4, 3, 2, 1, 1}, 그리고 선택 개수는 3을 넘겼을 때 결과는 아래와 같다.

1. 예제 결과

조합(Combination)하는 경우의 수 :
2,3,4
1,3,4
1,2,4
1,2,3
—–
순열(Permutation)하는 경우의 수 :
2,3,4
2,4,3
3,2,4
3,4,2
4,3,2
4,2,3
1,3,4
1,4,3
3,1,4
3,4,1
4,3,1
4,1,3
1,2,4
1,4,2
2,1,4
2,4,1
4,2,1
4,1,2
1,2,3
1,3,2
2,1,3
2,3,1
3,2,1
3,1,2

2. 순열(Permutation)하는 경우의 수를 가져오는 코드

    public static void main(String[] args) {
        int[] inputArray = {1, 2, 3, 4, 4, 3, 2, 1, 1};
        ArrayList<int[]> combinationList = getCombination(inputArray, 3);
        System.out.println(“조합(Combination)하는 경우의 수 : “);
        print(combinationList);
        System.out.println(“—–“);
        
        System.out.println(“순열(Permutation)하는 경우의 수 : “);
        ArrayList<int[]> permutationList = getPermutation(inputArray, 3);
        print(permutationList);
    }
    
    
    /**
     * 순열 구하기
     */

    public static ArrayList<int[]> getPermutation(int[] inputArray, int selectCount) {
        ArrayList<int[]> resultList = new ArrayList<int[]>();
        
        ArrayList<int[]> combinationList = getCombination(inputArray, selectCount);
        int count = combinationList.size();
        for (int i=0; i<count; i++) {
            if (combinationList.get(i) == null) {
                continue;
            }
            
            int[] singleArray = combinationList.get(i);
            singleArray = removeDuplication(singleArray);
            if (singleArray == null || singleArray.length == 0) {
                continue;
            }
            
            addSinglePermutation(resultList, singleArray, 0);
        }
        
        if (resultList != null) {
            resultList = removeDuplication(resultList);
        }
        
        return resultList;
    }
    
    
    /**
     * 순열 구하기
     */

    public static void addSinglePermutation(ArrayList<int[]> resultList, int[] inputArray, int position) {
        
        int count = inputArray.length;
        for (int i=position; i<count; i++) {
            int[] inputArray2 = cloneArray(inputArray);
            swapArray(inputArray2, position, i);
            resultList.add(inputArray);
            
            addSinglePermutation(resultList, inputArray2, position + 1);
        }
    }
    
    
    /**
     * 조합 구하기
     */

    public static ArrayList<int[]> getCombination(int[] inputArray, int selectCount) {
        // 미리 중복 제거하고 실행
        inputArray = removeDuplication(inputArray);
                
        ArrayList<int[]> resultList = new ArrayList<int[]>();
        int[] hintArray = new int[inputArray.length];
        
        addSingleCombination(resultList, selectCount, inputArray, 0, hintArray, 0);
        return resultList;
    }
    
    
    /**
     * 조합 구하기 재귀메서드
     */

    public static void addSingleCombination(ArrayList<int[]> resultList, int selectCount, int[] inputArray, int position, int[] hintArray, int hintIndex) {
        
        if (hintIndex == selectCount) {
            int[] singleArray = resizeIntArray(hintArray, hintIndex);
            if (singleArray != null && singleArray.length > 0) {
                resultList.add(singleArray);
            }
            return;
        }
        
        int lastIndex = inputArray.length – 1;
        if (position > lastIndex) {
            return;
        }
        
        int[] hintArray2 = cloneArray(hintArray);
        hintArray[hintIndex] = 0;
        hintArray2[hintIndex] = inputArray[position];
        
        position++;
        
        addSingleCombination(resultList, selectCount, inputArray, position, hintArray, hintIndex);
        addSingleCombination(resultList, selectCount, inputArray, position, hintArray2, hintIndex + 1);
    }
    
    
    /**
     * int 배열을 복제한다.
     */

    public static int[] cloneArray(int[] inputArray) {
        if (inputArray == null) {
            return null;
        }
        
        int count = inputArray.length;
        int[] resultArray = new int[count];
        
        for (int i=0; i<count; i++) {
            resultArray[i] = inputArray[i];
        }
        
        return resultArray;
    }
    
    
    /**
     * int 배열의 두 요소의 값을 맞바꾼다.
     */

    public static boolean swapArray(int[] array, int i, int k) {
        if (array == null) {
            return false;
        }
        
        int count = array.length;
        int lastIndex = count – 1;
        
        if (i < 0 || k < 0 || i > lastIndex || k > lastIndex) {
            return false;
        }
        
        int temp = array[i];
        array[i] = array[k];
        array[k] = temp;
        
        return true;
    }
    
    
    /**
     * int 배열의 길이를 변경한다. (조정한 배열을 새로 얻는다)
     */

    public static int[] resizeIntArray(int[] inputArray, int newSize) {
        int lastIndex = -1;
        if (inputArray != null) {
            lastIndex = inputArray.length – 1;
        }
        
        int[] newArray = new int[newSize];
        
        for (int i=0; i<newSize; i++) {
            if (i > lastIndex) {
                newArray[i] = 0;
            } else {
                newArray[i] = inputArray[i];
            }
        }
        
        return newArray;
    }
    
    
    /**
     * 인트 배열의 중복을 제거한다.
     */

    public static int[] removeDuplication(int[] inputArray) {
        if (inputArray == null || inputArray.length == 0) {
            return null;
        }
        
        int count = inputArray.length;
        int[] resultArray = new int[count];
        
        int currentIndex = 0;

        int singleItem = 0;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputArray[i];
            
            for (int k=0; k<resultArray.length; k++) {
                if (resultArray[k] == singleItem) {
                    continue oLoop;
                }
            }
            
            resultArray[currentIndex] = singleItem;
            currentIndex++;
        }
        
        if (currentIndex != count) {
            resultArray = resizeIntArray(resultArray, currentIndex);
        }
        
        return resultArray;
    }
    
    
    /**
     * Generic int 배열인 ArrayList 의 중복을 제거한다.
     */

    public static ArrayList<int[]> removeDuplication(ArrayList<int[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return null;
        }
        
        int count = inputList.size();
        ArrayList<int[]> resultList = new ArrayList<int[]>();
        
        int[] singleItem = null;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputList.get(i);
            
            for (int k=0; k<resultList.size(); k++) {
                if (equals(singleItem, resultList.get(k))) {
                    continue oLoop;
                }
            }
            
            resultList.add(singleItem);
        }
        
        return resultList;
    }
    
    
    /**
     * int 배열 2개가 서로 같은지 비교한다.
     */

    public static boolean equals(int[] array1, int[] array2) {
        if (array1 == null && array2 == null) {
            return true;
        }
        
        if (array1 == null || array2 == null) {
            return false;
        }
        
        if (array1.length != array2.length) {
            return false;
        }
        
        int count = array1.length;
        for (int i=0; i<count; i++) {
            if (array1[i] != array2[i]) {
                return false;
            }
        }
        
        return true;
    }
    
    
    /**
     * 제네릭이 int 배열인 ArrayList를 출력한다.
     */

    public static void print(ArrayList<int[]> inputList) {
        System.out.println(convertToString(inputList));
    }
    
    
    /**
     * int 배열을 출력한다.
     */

    public static void print(int[] inputArray) {
        System.out.println(convertToString(inputArray, “,”));
    }
    
    
    /**
     * 제네릭이 int 배열인 ArrayList의 내용을 문자열 형태로 만든다.
     */

    public static String convertToString(ArrayList<int[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputList.size();
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(“\n”);
            }
            
            buff.append(convertToString(inputList.get(i), “,”));
        }
        
        return buff.toString();
    }

    
    /**
     * int 배열의 내용을 문자열 형태로 만든다.
     */

    public static String convertToString(int[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }

[JAVA] 조합(Combination)하는 경우의 수

[JAVA] 조합(Combination)하는 경우의 수

모 기업 코딩테스트를 치른후 순열과 조합에 취약하다는 것을 깨닫고 실습을 위해 코드 작성.

(사실 순열조합 뿐만이 아니라 모든 알고리즘 문제에 취약)

int 배열과 선택 개수를 파라미터로 넘기면 모든 조합(Combination)하는 경우의 수를 가져오는 메서드 getCombination을 작성했다.

(참고로 조합은 순서가 없는 경우의 수. 순열은 순서가 있는 경우의 수)

예를 들어 int 배열은 {1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1, 4, 3, 2, 6, 5}, 그리고 선택 개수는 4를 넘겼을 때 결과는 아래와 같다.

1. 예제 결과

3,4,5,6
2,4,5,6
2,3,5,6
2,3,4,6
2,3,4,5
1,4,5,6
1,3,5,6
1,3,4,6
1,3,4,5
1,2,5,6
1,2,4,6
1,2,4,5
1,2,3,6
1,2,3,5
1,2,3,4

2. 조합(Combination) 경우의 수를 가져오는 코드

public static void main(String[] args) {
        int[] inputArray = {1, 2, 3, 4, 5, 6, 6, 5, 4, 3, 2, 1, 4, 3, 2, 6, 5};
        ArrayList<int[]> resultList = getCombination(inputArray, 4);
        print(resultList);
    }
    
    
    /**
     * 제네릭이 int 배열인 ArrayList를 출력한다.
     */

    public static void print(ArrayList<int[]> inputList) {
        System.out.println(convertToString(inputList));
    }
    
    
    /**
     * int 배열을 출력한다.
     */

    public static void print(int[] inputArray) {
        System.out.println(convertToString(inputArray, “,”));
    }
    
    
    /**
     * 제네릭이 int 배열인 ArrayList의 내용을 문자열 형태로 만든다.
     */

    public static String convertToString(ArrayList<int[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputList.size();
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(“\n”);
            }
            
            buff.append(convertToString(inputList.get(i), “,”));
        }
        
        return buff.toString();
    }

    
    /**
     * int 배열의 내용을 문자열 형태로 만든다.
     */

    public static String convertToString(int[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }

    
    /**
     * 조합 구하기
     */

    public static ArrayList<int[]> getCombination(int[] inputArray, int selectCount) {
        // 미리 중복 제거하고 실행
        inputArray = removeDuplication(inputArray);
                
        ArrayList<int[]> resultList = new ArrayList<int[]>();
        int[] hintArray = new int[inputArray.length];
        
        getSingleCombination(resultList, selectCount, inputArray, 0, hintArray, 0);
        return resultList;
    }
    
    
    /**
     * 조합 구하기 재귀메서드
     */

    public static void addSingleCombination(ArrayList<int[]> resultList, int selectCount, int[] inputArray, int position, int[] hintArray, int hintIndex) {
        
        if (hintIndex == selectCount) {
            int[] singleArray = resizeIntArray(hintArray, hintIndex);
            if (singleArray != null && singleArray.length > 0) {
                resultList.add(singleArray);
            }
            return;
        }
        
        int lastIndex = inputArray.length – 1;
        if (position > lastIndex) {
            return;
        }
        
        int[] hintArray2 = cloneArray(hintArray);
        hintArray[hintIndex] = 0;
        hintArray2[hintIndex] = inputArray[position];
        
        position++;
        
        addSingleCombination(resultList, selectCount, inputArray, position, hintArray, hintIndex);
        addSingleCombination(resultList, selectCount, inputArray, position, hintArray2, hintIndex + 1);
    }
    
    
    /**
     * int 배열을 복제한다.
     */

    public static int[] cloneArray(int[] inputArray) {
        if (inputArray == null) {
            return null;
        }
        
        int count = inputArray.length;
        int[] resultArray = new int[count];
        
        for (int i=0; i<count; i++) {
            resultArray[i] = inputArray[i];
        }
        
        return resultArray;
    }
    
    
    /**
     * int 배열의 길이를 변경한다. (조정한 배열을 새로 얻는다)
     */

    public static int[] resizeIntArray(int[] inputArray, int newSize) {
        int lastIndex = -1;
        if (inputArray != null) {
            lastIndex = inputArray.length – 1;
        }
        
        int[] newArray = new int[newSize];
        
        for (int i=0; i<newSize; i++) {
            if (i > lastIndex) {
                newArray[i] = 0;
            } else {
                newArray[i] = inputArray[i];
            }
        }
        
        return newArray;
    }
    
    
    /**
     * int 배열의 중복을 제거한다.
     */

    public static int[] removeDuplication(int[] inputArray) {
        if (inputArray == null || inputArray.length == 0) {
            return null;
        }
        
        int count = inputArray.length;
        int[] resultArray = new int[count];
        
        int currentIndex = 0;

        int singleItem = 0;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputArray[i];
            
            for (int k=0; k<resultArray.length; k++) {
                if (resultArray[k] == singleItem) {
                    continue oLoop;
                }
            }
            
            resultArray[currentIndex] = singleItem;
            currentIndex++;
        }
        
        if (currentIndex != count) {
            resultArray = resizeIntArray(resultArray, currentIndex);
        }
        
        return resultArray;
    }

[JAVA] 경우의 수 2

[JAVA] 경우의 수 2

모 기업 코딩테스트를 치른후 순열과 조합에 취약하다는 것을 깨닫고 실습을 위해 코드 작성.

(사실 순열조합 뿐만이 아니라 모든 알고리즘 문제에 취약)

1. 경우의 수 출력

일단 변수(스위치) 5개가 있고, 변수가 0 또는 1의 값을 가진다고 할 때, 변수 3개가 1인 경우의 수(ex: 11100)를 출력하는 코드를 작성했다.

    public static void main(String[] args) {
        printAllNumberOfCases(5, 3);
    }

    
    /**
     * 경우의 수 출력
     */

    public static void printAllNumberOfCases(int totalCount, int selectCount) {
        printSingleCase(
“”, 0, totalCount, selectCount);
    }
    
    
    public static void printSingleCase(String str, int position, int totalCount, int selectCount) {
        
        if (position == totalCount) {
            if (selectCount == 0) {
                System.out.println(str);
            }
            return;
        }
        
        position++;
        
        printSingleCase(str +
“0”, position, totalCount, selectCount);
        printSingleCase(str +
“1”, position, totalCount, selectCount – 1);
    }

결과는 아래와 같다.

00111
01011
01101
01110
10011
10101
10110
11001
11010
11100

2. 경우의 수 ArrayList 로 가져오기

위 코드는 System.out.println으로 곧바로 출력한다.

이 경우 데이터를 재활용할 수 없으므로 ArrayList로 가져오는 코드를 작성했다.

    public static void main(String[] args) {
        ArrayList<String> caseList = getAllNumberOfCases(5, 3);
        System.out.println(caseList);
    }
    
    
    /**
     * 경우의 수 어레이리스트로 반환
     */

    public static ArrayList<String> getAllNumberOfCases(int totalCount, int selectCount) {
        ArrayList<String> caseList = new ArrayList<String>();
        getSingleCase(caseList, “”, 0, totalCount, selectCount);
        return caseList;
    }
    
    
    public static void getSingleCase(ArrayList<String> strList, String str, int position, int totalCount, int selectCount) {
        
        if (position == totalCount) {
            if (selectCount == 0) {
                strList.add(str);
            }
            return;
        }
        
        position++;
        
        getSingleCase(strList, str + “0”, position, totalCount, selectCount);
        getSingleCase(strList, str + “1”, position, totalCount, selectCount – 1);
    }

결과는 아래와 같다.

[00111, 01011, 01101, 01110, 10011, 10101, 10110, 11001, 11010, 11100]

[JAVA] 경우의 수

[JAVA] 경우의 수

모 기업 코딩테스트를 치른후 순열과 조합에 취약하다는 것을 깨닫고 실습을 위해 코드 작성.

(사실 순열조합 뿐만이 아니라 모든 알고리즘 문제에 취약)

1. 경우의 수 출력

일단 변수(스위치) 5개가 있고, 변수가 0 또는 1의 값을 가진다고 할 때, 모든 경우의 수를 출력하는 코드를 작성했다.

    public static void main(String[] args) {
        printAllNumberOfCases(5);
    }

    
    /**
     * 모든 경우의 수 출력
     */

    public static void printAllNumberOfCases(int totalCount) {
        printSingleCase(“”, 0, totalCount);
    }
    
    
    public static void printSingleCase(String str, int position, int totalCount) {
        
        if (position == totalCount) {
            System.out.println(str);
            return;
        }
        
        position++;
        
        printSingleCase(str + “0”, position, totalCount);
        printSingleCase(str + “1”, position, totalCount);
    }

결과는 아래와 같다.

00000
00001
00010
00011
00100
00101
00110
00111
01000
01001
01010
01011
01100
01101
01110
01111
10000
10001
10010
10011
10100
10101
10110
10111
11000
11001
11010
11011
11100
11101
11110
11111

2. 경우의 수 ArrayList 로 가져오기

위 코드는 System.out.println으로 곧바로 출력한다.

이 경우 데이터를 재활용할 수 없으므로 ArrayList로 가져오는 코드를 작성했다.

    public static void main(String[] args) {
        ArrayList<String> caseList = getAllNumberOfCases(5);
        System.out.println(caseList);
    }
    
    
    /**
     * 모든 경우의 수 어레이리스트로 반환
     */

    public static ArrayList<String> getAllNumberOfCases(int totalCount) {
        ArrayList<String> caseList = new ArrayList<String>();
        getSingleCase(caseList, “”, 0, totalCount);
        return caseList;
    }
    
    
    public static void getSingleCase(ArrayList<String> strList, String str, int position, int totalCount) {
        
        if (position == totalCount) {
            strList.add(str);
            return;
        }
        
        position++;
        
        getSingleCase(strList, str + “0”, position, totalCount);
        getSingleCase(strList, str + “1”, position, totalCount);
    }

결과는 아래와 같다.

[00000, 00001, 00010, 00011, 00100, 00101, 00110, 00111, 01000, 01001, 01010, 01011, 01100, 01101, 01110, 01111, 10000, 10001, 10010, 10011, 10100, 10101, 10110, 10111, 11000, 11001, 11010, 11011, 11100, 11101, 11110, 11111]

[JAVA] Generic String 배열인 ArrayList의 중복제거

[JAVA] Generic String 배열인 ArrayList의 중복제거

String 배열의 중복제거는 여기로 => [JAVA] String 배열의 중복제거 (https://blog.naver.com/bb_/221337106138)

 

Generic String 배열인 ArrayList의 중복제거 코드.

흔하게 필요하지는 않겠지만, 유사시 사용하기 위해 만들었다.

수행 결과는 다음과 같다.

 

before : 서울,부산,포항,대전,전주,나주,부산,포항,대전,전주
서울,부산,포항,대전,전주,나주,부산,포항,대전,전주
서울,부산,포항,대전,전주,나주,부산,포항,대전
서울,부산,포항,대전,전주
서울,부산,포항,대전,전주
 
after : 서울,부산,포항,대전,전주,나주,부산,포항,대전,전주
서울,부산,포항,대전,전주,나주,부산,포항,대전
서울,부산,포항,대전,전주

 

    public static void main(String[] args) {
        
        String[] intArray1 = {“서울”, “부산”, “포항”, “대전”, “전주”, “나주”, “부산”, “포항”, “대전”, “전주”};
        String[] intArray2 = {“서울”, “부산”, “포항”, “대전”, “전주”, “나주”, “부산”, “포항”, “대전”, “전주”};
        String[] intArray3 = {“서울”, “부산”, “포항”, “대전”, “전주”, “나주”, “부산”, “포항”, “대전”};
        String[] intArray4 = {“서울”, “부산”, “포항”, “대전”, “전주”};
        String[] intArray5 = {“서울”, “부산”, “포항”, “대전”, “전주”};
        
        ArrayList<String[]> intArrayList = new ArrayList<String[]>();
        intArrayList.add(intArray1);
        intArrayList.add(intArray2);
        intArrayList.add(intArray3);
        intArrayList.add(intArray4);
        intArrayList.add(intArray5);
        
        ArrayList<String[]> resultList = removeDuplication(intArrayList);
        
        System.out.println(“before : “ + convertToString(intArrayList));
        System.out.println(” “);
        System.out.println(“after : “ + convertToString(resultList));
    }
    
    
    /**
     * Generic String 배열인 ArrayList 를 출력용 스트링으로 변환한다.
     */

    public static String convertToString(ArrayList<String[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputList.size();
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(“\n”);
            }
            
            buff.append(convertToString(inputList.get(i), “,”));
        }
        
        return buff.toString();
    }
    
    
    /**
     * String 배열을 출력용 스트링으로 변환한다.
     */

    public static String convertToString(String[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }
    
    
    /**
     * Generic String 배열인 ArrayList 의 중복을 제거한다.
     */

    public static ArrayList<String[]> removeDuplication(ArrayList<String[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return null;
        }
        
        int count = inputList.size();
        ArrayList<String[]> resultList = new ArrayList<String[]>();
        
        String[] singleItem = null;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputList.get(i);
            
            for (int k=0; k<resultList.size(); k++) {
                if (equals(singleItem, resultList.get(k))) {
                    continue oLoop;
                }
            }
            
            resultList.add(singleItem);
        }
        
        return resultList;
    }
    
    
    /**
     * String 배열이 같은지 비교한다.
     */

    public static boolean equals(String[] arr1, String[] arr2) {
        if (arr1 == null && arr2 == null) {
            return true;
        }
        
        if (arr1 == null || arr2 == null) {
            return false;
        }
        
        if (arr1.length != arr2.length) {
            return false;
        }
        
        int count = arr1.length;
        for (int i=0; i<count; i++) {
            if (!arr1[i].equals(arr2[i])) {
                return false;
            }
        }
        
        return true;
    }

[JAVA] Generic int 배열인 ArrayList의 중복제거

[JAVA] Generic int 배열인 ArrayList의 중복제거

int 배열의 중복제거는 여기로 => [JAVA] int 배열의 중복제거 (http://blog.naver.com/bb_/221337104683)

Generic int 배열인 ArrayList의 중복제거 코드.

흔하게 필요하지는 않겠지만, 유사시 사용하기 위해 만들었다.

수행 결과는 다음과 같다.

before : 1,2,3,4,5,1,2,3,4,5
1,2,3,4,5,1,2,3,4,5
1,2,3,4,5,1,2,3,4
1,2,3,4,5
1,2,3,4,5
 
after : 1,2,3,4,5,1,2,3,4,5
1,2,3,4,5,1,2,3,4
1,2,3,4,5

    public static void main(String[] args) {
        
        int[] intArray1 = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5};
        int[] intArray2 = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5};
        int[] intArray3 = {1, 2, 3, 4, 5, 1, 2, 3, 4};
        int[] intArray4 = {1, 2, 3, 4, 5};
        int[] intArray5 = {1, 2, 3, 4, 5};
        
        ArrayList<int[]> intArrayList = new ArrayList<int[]>();
        intArrayList.add(intArray1);
        intArrayList.add(intArray2);
        intArrayList.add(intArray3);
        intArrayList.add(intArray4);
        intArrayList.add(intArray5);
        
        ArrayList<int[]> resultList = removeDuplication(intArrayList);
        
        System.out.println(“before : “ + convertToString(intArrayList));
        System.out.println(” “);
        System.out.println(“after : “ + convertToString(resultList));
    }
    
    
    /**
     * Generic int 배열인 ArrayList 를 출력용 스트링으로 변환한다.
     */

    public static String convertToString(ArrayList<int[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputList.size();
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(“\n”);
            }
            
            buff.append(convertToString(inputList.get(i), “,”));
        }
        
        return buff.toString();
    }
    
    
    /**
     * int 배열을 출력용 스트링으로 변환한다.
     */

    public static String convertToString(int[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }
    
    
    /**
     * Generic int 배열인 ArrayList 의 중복을 제거한다.
     */

    public static ArrayList<int[]> removeDuplication(ArrayList<int[]> inputList) {
        if (inputList == null || inputList.size() == 0) {
            return null;
        }
        
        int count = inputList.size();
        ArrayList<int[]> resultList = new ArrayList<int[]>();
        
        int[] singleItem = null;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputList.get(i);
            
            for (int k=0; k<resultList.size(); k++) {
                if (equals(singleItem, resultList.get(k))) {
                    continue oLoop;
                }
            }
            
            resultList.add(singleItem);
        }
        
        return resultList;
    }
    
    
    /**
     * int 배열이 같은지 비교한다.
     */

    public static boolean equals(int[] arr1, int[] arr2) {
        if (arr1 == null && arr2 == null) {
            return true;
        }
        
        if (arr1 == null || arr2 == null) {
            return false;
        }
        
        if (arr1.length != arr2.length) {
            return false;
        }
        
        int count = arr1.length;
        for (int i=0; i<count; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        
        return true;
    }

[JAVA] String 배열의 중복제거

[JAVA] String 배열의 중복제거

 

유사시 사용하기 위해 만든 String 배열의 중복제거 코드.

수행 결과는 다음과 같다.

 

before : 서울,부산,대구,전주,대전,제주,서울,대전,제주
after : 서울,부산,대구,전주,대전,제주

 

    public static void main(String[] args) {
        
        String[] strArray = {“서울”, “부산”, “대구”, “전주”, “대전”, “제주”, “서울”, “대전”, “제주”};
        String[] resultArray = removeDuplication(strArray);
        
        System.out.println(“before : “ + getString(strArray, “,”));
        System.out.println(“after : “ + getString(resultArray, “,”));
    }
    
    
    /**
     * 스트링 배열을 출력하기 쉽도록 한줄 스트링 형태로 변환한다.
     */

    public static String getString(String[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff = new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }
    
    
    /**
     * 스트링 배열의 중복을 제거한다.
     */

    public static String[] removeDuplication(String[] inputArray) {
        if (inputArray == null || inputArray.length == 0) {
            return null;
        }
        
        int count = inputArray.length;
        String[] resultArray = new String[count];
        
        int currentIndex = 0;
        
        String singleItem = null;
        
        oLoop : for (int i=0; i<count; i++) {
            singleItem = inputArray[i];
            
            for (int k=0; k<resultArray.length; k++) {
                if (resultArray[k] == singleItem) {
                    continue oLoop;
                }
            }
            
            resultArray[currentIndex] = singleItem;
            currentIndex++;
        }
        
        if (currentIndex != count) {
            resultArray = resizeIntArray(resultArray, currentIndex);
        }
        
        return resultArray;
    }
    
    
    /**
     * 스트링 배열의 길이를 조정한다. (조정한 배열을 새로 얻는다)
     */

    public static String[] resizeIntArray(String[] inputArray, int newSize) {
        int lastIndex = -1;
        if (inputArray != null) {
            lastIndex = inputArray.length – 1;
        }
        
        String[] newArray = new String[newSize];
        
        for (int i=0; i<newSize; i++) {
            if (i > lastIndex) {
                newArray[i] = “”;
            } else {
                if (inputArray[i] == null) {
                    newArray[i] = “”;
                } else {
                    newArray[i] = inputArray[i];
                }
            }
        }
        
        return newArray;
    }

[JAVA] int 배열의 중복제거

[JAVA] int 배열의 중복제거

 

유사시 사용하기 위해 만든 int 배열의 중복제거 코드.

수행 결과는 다음과 같다.

 

before : 1,2,3,4,5,1,2,3,4,5
after : 1,2,3,4,5

 

    public static void main(String[] args) {
        
        int[] intArray = {1, 2, 3, 4, 5, 1, 2, 3, 4, 5};
        int[] resultArray = removeDuplication(intArray);
        
        System.out.println(
“before : “ + convertToString(intArray, “,”));
        System.out.println(
“after : “ + convertToString(resultArray, “,”));
    }
    
    
    /**
     * 인트 배열을 출력하기 쉽도록 한줄 스트링 형태로 변환한다.
     */

    public static String convertToString(int[] inputArray, String delimiter) {
        if (inputArray == null || inputArray.length == 0) {
            return “”;
        }
        
        StringBuffer buff =
new StringBuffer();
        
        int count = inputArray.length;
        for (int i=0; i<count; i++) {
            if (buff.length() > 0) {
                buff.append(delimiter);
            }
            
            buff.append(inputArray[i]);
        }
        
        return buff.toString();
    }
    
    
    /**
     * 인트 배열의 중복을 제거한다.
     */

    public static int[] removeDuplication(int[] inputArray) {
        if (inputArray == null || inputArray.length == 0) {
            return null;
        }
        
        int count = inputArray.length;
        int[] resultArray =
new int[count];
        
        int currentIndex = 0;

        int singleItem = 0;
        
        oLoop :
for (int i=0; i<count; i++) {
            singleItem = inputArray[i];
            
            for (int k=0; k<resultArray.length; k++) {
                if (resultArray[k] == singleItem) {
                    continue oLoop;
                }
            }
            
            resultArray[currentIndex] = singleItem;
            currentIndex++;
        }
        
        if (currentIndex != count) {
            resultArray = resizeIntArray(resultArray, currentIndex);
        }
        
        return resultArray;
    }
    
    
    /**
     * 인트 배열의 길이를 조정한다. (조정한 배열을 새로 얻는다)
     */

    public static int[] resizeIntArray(int[] inputArray, int newSize) {
        int lastIndex = -1;
        if (inputArray != null) {
            lastIndex = inputArray.length – 1;
        }
        
        int[] newArray =
new int[newSize];
        
        for (int i=0; i<newSize; i++) {
            if (i > lastIndex) {
                newArray[i] = 0;
            }
else {
                newArray[i] = inputArray[i];
            }
        }
        
        return newArray;
    }

[javascript] js select 선택 / javascript select 선택 / 자바스크립트 select박스 선택

[javascript] js select 선택 / javascript select 선택 / 자바스크립트 select박스 선택


​select 박스, 콤보박스라고도 부른다.

jquery로 제어하면 편하지만, 여기서는 순수 javascript로 제어하는 함수를 적어둔다.

// getComboSelectedId : select 태그의 아이디를 파라미터로 넘기면, 현재 선택되어 있는 인덱스를 반환한다.

function getComboSelectedId(_comboId) {
    var comboObj = document.getElementById(_comboId);
    if (comboObj == null) {
        return null;
    }
            
    if (comboObj.options == null || comboObj.options.length == 0) {
        return null;
    }
                    
    var optionCount = comboObj.options.length;
    for (var i=0; i<optionCount; i++) {
        if (comboObj.options[i].selected) {
            return comboObj.options[i].id;
        }
    }
        
    return null;
}
 

// setComboToSelectOption : select 태그의 아이디와 option 태그의 아이디를 파라미터로 넘기면, 해당 option을 선택한다.
function setComboToSelectOption(_comboId, _itemId) {
    var comboObj = document.getElementById(_comboId);
    if (comboObj == null) {
        return false;
    }
            
    if (comboObj.options == null || comboObj.options.length == 0) {
        return false;
    }
    
    if (_itemId == null) {
        _itemId = “”;
    }
                    
    var optionCount = comboObj.options.length;
    for (var i=0; i<optionCount; i++) {
        if (comboObj.options[i].id == _itemId) {
            comboObj.options[i].selected = true;
            return true;
        }
    }
        
    return false;
}
 

프로필 이미지 변경

프로필 이미지 변경

프로필 이미지 변경하였습니다.

그림판으로 그렸던 이미지를, 포토샵으로 깔끔하게 다듬었습니다.

1. 그림판으로 그렸던 이미지

2. 포토샵으로 다듬은 이미지

 

그림판으로 그렸던 이미지(profileimg.jpg)와

포토샵으로 다듬은 이미지(bbear.jpg, bbear.psd) 등을 bbear.zip 파일로 압축해서 첨부해둡니다.

백업 차원이죠.

[javascript] AES128 암호화 예제

[javascript] AES128 암호화 예제

[JAVA] AES128 암호화 예제 바로가기 => https://blog.naver.com/bb_/221332286531

[JAVA/JS] AES128 암호화 예제 github 소스 바로 가기 => https://github.com/thkmon/AES128Test

자바 AES128 암호화와 호환되는 자바스크립트 AES128 암호화 및 복호화 예제이다. AES128은 양방향 암호화를 지원한다. 즉, 키값을 안다면, 암호화한 스트링을 다시 복호화할 수 있다.

자바에서 키값(iv값, salt값)을 생성해서 클라이언트로 내려주는 것을 권장한다.

1. 소스코드
1-1. aes_util.js
인터넷에 돌아다니는 js 소스 3개를 합쳐서 aes_util.js 를 만들었다. AesUtil.js, aes.js, pbkdf2.js, 이 3개 파일을 하나로 합쳤다. 왠지 찜찜해서 저작권 주석표시는 지우지 않았다. 아마 구글의 제프 못(Jeff Mott)님께서 만드신 것으로 추측되는데, 감사드린다.

//[AesUtil.js]
var AesUtil=function(keySize,iterationCount){this.keySize=keySize/32;this.iterationCount=iterationCount;}
AesUtil.prototype.generateKey=function(salt,passPhrase) {var key=CryptoJS.PBKDF2(passPhrase,CryptoJS.enc.Hex.parse(salt),{keySize: this.keySize,iterations: this.iterationCount});return key;}
AesUtil.prototype.encrypt=function(salt,iv,passPhrase,plainText){var key=this.generateKey(salt,passPhrase);var encrypted=CryptoJS.AES.encrypt(plainText,key,{iv: CryptoJS.enc.Hex.parse(iv)});return encrypted.ciphertext.toString(CryptoJS.enc.Base64);}
AesUtil.prototype.decrypt=function(salt,iv,passPhrase,cipherText){var key=this.generateKey(salt,passPhrase);var cipherParams=CryptoJS.lib.CipherParams.create({ciphertext: CryptoJS.enc.Base64.parse(cipherText)});var decrypted=CryptoJS.AES.decrypt(cipherParams,key,{iv: CryptoJS.enc.Hex.parse(iv)});return decrypted.toString(CryptoJS.enc.Utf8);}
//[aes.js]
/*
CryptoJS v3.0.2
code.google.com/p/crypto-js
(c) 2009-2012 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(p,h){var i={},l=i.lib={},r=l.Base=function(){function a(){}return{extend:function(e){a.prototype=this;var c=new a;e&&c.mixIn(e);c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty(“toString”)&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),o=l.WordArray=r.extend({init:function(a,e){a=
this.words=a||[];this.sigBytes=e!=h?e:4*a.length},toString:function(a){return(a||s).stringify(this)},concat:function(a){var e=this.words,c=a.words,b=this.sigBytes,a=a.sigBytes;this.clamp();if(b%4)for(var d=0;d<a;d++)e[b+d>>>2]|=(c[d>>>2]>>>24-8*(d%4)&255)<<24-8*((b+d)%4);else if(65535<c.length)for(d=0;d<a;d+=4)e[b+d>>>2]=c[d>>>2];else e.push.apply(e,c);this.sigBytes+=a;return this},clamp:function(){var a=this.words,e=this.sigBytes;a[e>>>2]&=4294967295<<32-8*(e%4);a.length=p.ceil(e/4)},clone:function(){var a=
r.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var e=[],c=0;c<a;c+=4)e.push(4294967296*p.random()|0);return o.create(e,a)}}),m=i.enc={},s=m.Hex={stringify:function(a){for(var e=a.words,a=a.sigBytes,c=[],b=0;b<a;b++){var d=e[b>>>2]>>>24-8*(b%4)&255;c.push((d>>>4).toString(16));c.push((d&15).toString(16))}return c.join(“”)},parse:function(a){for(var e=a.length,c=[],b=0;b<e;b+=2)c[b>>>3]|=parseInt(a.substr(b,2),16)<<24-4*(b%8);return o.create(c,e/2)}},n=m.Latin1={stringify:function(a){for(var e=
a.words,a=a.sigBytes,c=[],b=0;b<a;b++)c.push(String.fromCharCode(e[b>>>2]>>>24-8*(b%4)&255));return c.join(“”)},parse:function(a){for(var e=a.length,c=[],b=0;b<e;b++)c[b>>>2]|=(a.charCodeAt(b)&255)<<24-8*(b%4);return o.create(c,e)}},k=m.Utf8={stringify:function(a){try{return decodeURIComponent(escape(n.stringify(a)))}catch(e){throw Error(“Malformed UTF-8 data”);}},parse:function(a){return n.parse(unescape(encodeURIComponent(a)))}},f=l.BufferedBlockAlgorithm=r.extend({reset:function(){this._data=o.create();
this._nDataBytes=0},_append:function(a){“string”==typeof a&&(a=k.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var e=this._data,c=e.words,b=e.sigBytes,d=this.blockSize,q=b/(4*d),q=a?p.ceil(q):p.max((q|0)-this._minBufferSize,0),a=q*d,b=p.min(4*a,b);if(a){for(var j=0;j<a;j+=d)this._doProcessBlock(c,j);j=c.splice(0,a);e.sigBytes-=b}return o.create(j,b)},clone:function(){var a=r.clone.call(this);a._data=this._data.clone();return a},_minBufferSize:0});l.Hasher=f.extend({init:function(){this.reset()},
reset:function(){f.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);this._doFinalize();return this._hash},clone:function(){var a=f.clone.call(this);a._hash=this._hash.clone();return a},blockSize:16,_createHelper:function(a){return function(e,c){return a.create(c).finalize(e)}},_createHmacHelper:function(a){return function(e,c){return g.HMAC.create(a,c).finalize(e)}}});var g=i.algo={};return i}(Math);
(function(){var p=CryptoJS,h=p.lib.WordArray;p.enc.Base64={stringify:function(i){var l=i.words,h=i.sigBytes,o=this._map;i.clamp();for(var i=[],m=0;m<h;m+=3)for(var s=(l[m>>>2]>>>24-8*(m%4)&255)<<16|(l[m+1>>>2]>>>24-8*((m+1)%4)&255)<<8|l[m+2>>>2]>>>24-8*((m+2)%4)&255,n=0;4>n&&m+0.75*n<h;n++)i.push(o.charAt(s>>>6*(3-n)&63));if(l=o.charAt(64))for(;i.length%4;)i.push(l);return i.join(“”)},parse:function(i){var i=i.replace(/\s/g,””),l=i.length,r=this._map,o=r.charAt(64);o&&(o=i.indexOf(o),-1!=o&&(l=o));
for(var o=[],m=0,s=0;s<l;s++)if(s%4){var n=r.indexOf(i.charAt(s-1))<<2*(s%4),k=r.indexOf(i.charAt(s))>>>6-2*(s%4);o[m>>>2]|=(n|k)<<24-8*(m%4);m++}return h.create(o,m)},_map:”ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=”}})();
(function(p){function h(f,g,a,e,c,b,d){f=f+(g&a|~g&e)+c+d;return(f<<b|f>>>32-b)+g}function i(f,g,a,e,c,b,d){f=f+(g&e|a&~e)+c+d;return(f<<b|f>>>32-b)+g}function l(f,g,a,e,c,b,d){f=f+(g^a^e)+c+d;return(f<<b|f>>>32-b)+g}function r(f,g,a,e,c,b,d){f=f+(a^(g|~e))+c+d;return(f<<b|f>>>32-b)+g}var o=CryptoJS,m=o.lib,s=m.WordArray,m=m.Hasher,n=o.algo,k=[];(function(){for(var f=0;64>f;f++)k[f]=4294967296*p.abs(p.sin(f+1))|0})();n=n.MD5=m.extend({_doReset:function(){this._hash=s.create([1732584193,4023233417,
2562383102,271733878])},_doProcessBlock:function(f,g){for(var a=0;16>a;a++){var e=g+a,c=f[e];f[e]=(c<<8|c>>>24)&16711935|(c<<24|c>>>8)&4278255360}for(var e=this._hash.words,c=e[0],b=e[1],d=e[2],q=e[3],a=0;64>a;a+=4)16>a?(c=h(c,b,d,q,f[g+a],7,k[a]),q=h(q,c,b,d,f[g+a+1],12,k[a+1]),d=h(d,q,c,b,f[g+a+2],17,k[a+2]),b=h(b,d,q,c,f[g+a+3],22,k[a+3])):32>a?(c=i(c,b,d,q,f[g+(a+1)%16],5,k[a]),q=i(q,c,b,d,f[g+(a+6)%16],9,k[a+1]),d=i(d,q,c,b,f[g+(a+11)%16],14,k[a+2]),b=i(b,d,q,c,f[g+a%16],20,k[a+3])):48>a?(c=
l(c,b,d,q,f[g+(3*a+5)%16],4,k[a]),q=l(q,c,b,d,f[g+(3*a+8)%16],11,k[a+1]),d=l(d,q,c,b,f[g+(3*a+11)%16],16,k[a+2]),b=l(b,d,q,c,f[g+(3*a+14)%16],23,k[a+3])):(c=r(c,b,d,q,f[g+3*a%16],6,k[a]),q=r(q,c,b,d,f[g+(3*a+7)%16],10,k[a+1]),d=r(d,q,c,b,f[g+(3*a+14)%16],15,k[a+2]),b=r(b,d,q,c,f[g+(3*a+5)%16],21,k[a+3]));e[0]=e[0]+c|0;e[1]=e[1]+b|0;e[2]=e[2]+d|0;e[3]=e[3]+q|0},_doFinalize:function(){var f=this._data,g=f.words,a=8*this._nDataBytes,e=8*f.sigBytes;g[e>>>5]|=128<<24-e%32;g[(e+64>>>9<<4)+14]=(a<<8|a>>>
24)&16711935|(a<<24|a>>>8)&4278255360;f.sigBytes=4*(g.length+1);this._process();f=this._hash.words;for(g=0;4>g;g++)a=f[g],f[g]=(a<<8|a>>>24)&16711935|(a<<24|a>>>8)&4278255360}});o.MD5=m._createHelper(n);o.HmacMD5=m._createHmacHelper(n)})(Math);
(function(){var p=CryptoJS,h=p.lib,i=h.Base,l=h.WordArray,h=p.algo,r=h.EvpKDF=i.extend({cfg:i.extend({keySize:4,hasher:h.MD5,iterations:1}),init:function(i){this.cfg=this.cfg.extend(i)},compute:function(i,m){for(var h=this.cfg,n=h.hasher.create(),k=l.create(),f=k.words,g=h.keySize,h=h.iterations;f.length<g;){a&&n.update(a);var a=n.update(i).finalize(m);n.reset();for(var e=1;e<h;e++)a=n.finalize(a),n.reset();k.concat(a)}k.sigBytes=4*g;return k}});p.EvpKDF=function(i,l,h){return r.create(h).compute(i,
l)}})();
CryptoJS.lib.Cipher||function(p){var h=CryptoJS,i=h.lib,l=i.Base,r=i.WordArray,o=i.BufferedBlockAlgorithm,m=h.enc.Base64,s=h.algo.EvpKDF,n=i.Cipher=o.extend({cfg:l.extend(),createEncryptor:function(b,d){return this.create(this._ENC_XFORM_MODE,b,d)},createDecryptor:function(b,d){return this.create(this._DEC_XFORM_MODE,b,d)},init:function(b,d,a){this.cfg=this.cfg.extend(a);this._xformMode=b;this._key=d;this.reset()},reset:function(){o.reset.call(this);this._doReset()},process:function(b){this._append(b);return this._process()},
finalize:function(b){b&&this._append(b);return this._doFinalize()},keySize:4,ivSize:4,_ENC_XFORM_MODE:1,_DEC_XFORM_MODE:2,_createHelper:function(){return function(b){return{encrypt:function(a,q,j){return(“string”==typeof q?c:e).encrypt(b,a,q,j)},decrypt:function(a,q,j){return(“string”==typeof q?c:e).decrypt(b,a,q,j)}}}}()});i.StreamCipher=n.extend({_doFinalize:function(){return this._process(!0)},blockSize:1});var k=h.mode={},f=i.BlockCipherMode=l.extend({createEncryptor:function(b,a){return this.Encryptor.create(b,
a)},createDecryptor:function(b,a){return this.Decryptor.create(b,a)},init:function(b,a){this._cipher=b;this._iv=a}}),k=k.CBC=function(){function b(b,a,d){var c=this._iv;c?this._iv=p:c=this._prevBlock;for(var e=0;e<d;e++)b[a+e]^=c[e]}var a=f.extend();a.Encryptor=a.extend({processBlock:function(a,d){var c=this._cipher,e=c.blockSize;b.call(this,a,d,e);c.encryptBlock(a,d);this._prevBlock=a.slice(d,d+e)}});a.Decryptor=a.extend({processBlock:function(a,d){var c=this._cipher,e=c.blockSize,f=a.slice(d,d+
e);c.decryptBlock(a,d);b.call(this,a,d,e);this._prevBlock=f}});return a}(),g=(h.pad={}).Pkcs7={pad:function(b,a){for(var c=4*a,c=c-b.sigBytes%c,e=c<<24|c<<16|c<<8|c,f=[],g=0;g<c;g+=4)f.push(e);c=r.create(f,c);b.concat(c)},unpad:function(b){b.sigBytes-=b.words[b.sigBytes-1>>>2]&255}};i.BlockCipher=n.extend({cfg:n.cfg.extend({mode:k,padding:g}),reset:function(){n.reset.call(this);var b=this.cfg,a=b.iv,b=b.mode;if(this._xformMode==this._ENC_XFORM_MODE)var c=b.createEncryptor;else c=b.createDecryptor,
this._minBufferSize=1;this._mode=c.call(b,this,a&&a.words)},_doProcessBlock:function(b,a){this._mode.processBlock(b,a)},_doFinalize:function(){var b=this.cfg.padding;if(this._xformMode==this._ENC_XFORM_MODE){b.pad(this._data,this.blockSize);var a=this._process(!0)}else a=this._process(!0),b.unpad(a);return a},blockSize:4});var a=i.CipherParams=l.extend({init:function(a){this.mixIn(a)},toString:function(a){return(a||this.formatter).stringify(this)}}),k=(h.format={}).OpenSSL={stringify:function(a){var d=
a.ciphertext,a=a.salt,d=(a?r.create([1398893684,1701076831]).concat(a).concat(d):d).toString(m);return d=d.replace(/(.{64})/g,”$1\n”)},parse:function(b){var b=m.parse(b),d=b.words;if(1398893684==d[0]&&1701076831==d[1]){var c=r.create(d.slice(2,4));d.splice(0,4);b.sigBytes-=16}return a.create({ciphertext:b,salt:c})}},e=i.SerializableCipher=l.extend({cfg:l.extend({format:k}),encrypt:function(b,d,c,e){var e=this.cfg.extend(e),f=b.createEncryptor(c,e),d=f.finalize(d),f=f.cfg;return a.create({ciphertext:d,
key:c,iv:f.iv,algorithm:b,mode:f.mode,padding:f.padding,blockSize:b.blockSize,formatter:e.format})},decrypt:function(a,c,e,f){f=this.cfg.extend(f);c=this._parse(c,f.format);return a.createDecryptor(e,f).finalize(c.ciphertext)},_parse:function(a,c){return”string”==typeof a?c.parse(a):a}}),h=(h.kdf={}).OpenSSL={compute:function(b,c,e,f){f||(f=r.random(8));b=s.create({keySize:c+e}).compute(b,f);e=r.create(b.words.slice(c),4*e);b.sigBytes=4*c;return a.create({key:b,iv:e,salt:f})}},c=i.PasswordBasedCipher=
e.extend({cfg:e.cfg.extend({kdf:h}),encrypt:function(a,c,f,j){j=this.cfg.extend(j);f=j.kdf.compute(f,a.keySize,a.ivSize);j.iv=f.iv;a=e.encrypt.call(this,a,c,f.key,j);a.mixIn(f);return a},decrypt:function(a,c,f,j){j=this.cfg.extend(j);c=this._parse(c,j.format);f=j.kdf.compute(f,a.keySize,a.ivSize,c.salt);j.iv=f.iv;return e.decrypt.call(this,a,c,f.key,j)}})}();
(function(){var p=CryptoJS,h=p.lib.BlockCipher,i=p.algo,l=[],r=[],o=[],m=[],s=[],n=[],k=[],f=[],g=[],a=[];(function(){for(var c=[],b=0;256>b;b++)c[b]=128>b?b<<1:b<<1^283;for(var d=0,e=0,b=0;256>b;b++){var j=e^e<<1^e<<2^e<<3^e<<4,j=j>>>8^j&255^99;l[d]=j;r[j]=d;var i=c[d],h=c[i],p=c[h],t=257*c[j]^16843008*j;o[d]=t<<24|t>>>8;m[d]=t<<16|t>>>16;s[d]=t<<8|t>>>24;n[d]=t;t=16843009*p^65537*h^257*i^16843008*d;k[j]=t<<24|t>>>8;f[j]=t<<16|t>>>16;g[j]=t<<8|t>>>24;a[j]=t;d?(d=i^c[c[c[p^i]]],e^=c[c[e]]):d=e=1}})();
var e=[0,1,2,4,8,16,32,64,128,27,54],i=i.AES=h.extend({_doReset:function(){for(var c=this._key,b=c.words,d=c.sigBytes/4,c=4*((this._nRounds=d+6)+1),i=this._keySchedule=[],j=0;j<c;j++)if(j<d)i[j]=b[j];else{var h=i[j-1];j%d?6<d&&4==j%d&&(h=l[h>>>24]<<24|l[h>>>16&255]<<16|l[h>>>8&255]<<8|l[h&255]):(h=h<<8|h>>>24,h=l[h>>>24]<<24|l[h>>>16&255]<<16|l[h>>>8&255]<<8|l[h&255],h^=e[j/d|0]<<24);i[j]=i[j-d]^h}b=this._invKeySchedule=[];for(d=0;d<c;d++)j=c-d,h=d%4?i[j]:i[j-4],b[d]=4>d||4>=j?h:k[l[h>>>24]]^f[l[h>>>
16&255]]^g[l[h>>>8&255]]^a[l[h&255]]},encryptBlock:function(a,b){this._doCryptBlock(a,b,this._keySchedule,o,m,s,n,l)},decryptBlock:function(c,b){var d=c[b+1];c[b+1]=c[b+3];c[b+3]=d;this._doCryptBlock(c,b,this._invKeySchedule,k,f,g,a,r);d=c[b+1];c[b+1]=c[b+3];c[b+3]=d},_doCryptBlock:function(a,b,d,e,f,h,i,g){for(var l=this._nRounds,k=a[b]^d[0],m=a[b+1]^d[1],o=a[b+2]^d[2],n=a[b+3]^d[3],p=4,r=1;r<l;r++)var s=e[k>>>24]^f[m>>>16&255]^h[o>>>8&255]^i[n&255]^d[p++],u=e[m>>>24]^f[o>>>16&255]^h[n>>>8&255]^
i[k&255]^d[p++],v=e[o>>>24]^f[n>>>16&255]^h[k>>>8&255]^i[m&255]^d[p++],n=e[n>>>24]^f[k>>>16&255]^h[m>>>8&255]^i[o&255]^d[p++],k=s,m=u,o=v;s=(g[k>>>24]<<24|g[m>>>16&255]<<16|g[o>>>8&255]<<8|g[n&255])^d[p++];u=(g[m>>>24]<<24|g[o>>>16&255]<<16|g[n>>>8&255]<<8|g[k&255])^d[p++];v=(g[o>>>24]<<24|g[n>>>16&255]<<16|g[k>>>8&255]<<8|g[m&255])^d[p++];n=(g[n>>>24]<<24|g[k>>>16&255]<<16|g[m>>>8&255]<<8|g[o&255])^d[p++];a[b]=s;a[b+1]=u;a[b+2]=v;a[b+3]=n},keySize:8});p.AES=h._createHelper(i)})();
//[pbkdf2.js]
/*
CryptoJS v3.0.2
code.google.com/p/crypto-js
(c) 2009-2012 by Jeff Mott. All rights reserved.
code.google.com/p/crypto-js/wiki/License
*/
var CryptoJS=CryptoJS||function(g,i){var f={},b=f.lib={},m=b.Base=function(){function a(){}return{extend:function(e){a.prototype=this;var c=new a;e&&c.mixIn(e);c.$super=this;return c},create:function(){var a=this.extend();a.init.apply(a,arguments);return a},init:function(){},mixIn:function(a){for(var c in a)a.hasOwnProperty(c)&&(this[c]=a[c]);a.hasOwnProperty(“toString”)&&(this.toString=a.toString)},clone:function(){return this.$super.extend(this)}}}(),l=b.WordArray=m.extend({init:function(a,e){a=
this.words=a||[];this.sigBytes=e!=i?e:4*a.length},toString:function(a){return(a||d).stringify(this)},concat:function(a){var e=this.words,c=a.words,o=this.sigBytes,a=a.sigBytes;this.clamp();if(o%4)for(var b=0;b<a;b++)e[o+b>>>2]|=(c[b>>>2]>>>24-8*(b%4)&255)<<24-8*((o+b)%4);else if(65535<c.length)for(b=0;b<a;b+=4)e[o+b>>>2]=c[b>>>2];else e.push.apply(e,c);this.sigBytes+=a;return this},clamp:function(){var a=this.words,e=this.sigBytes;a[e>>>2]&=4294967295<<32-8*(e%4);a.length=g.ceil(e/4)},clone:function(){var a=
m.clone.call(this);a.words=this.words.slice(0);return a},random:function(a){for(var e=[],c=0;c<a;c+=4)e.push(4294967296*g.random()|0);return l.create(e,a)}}),n=f.enc={},d=n.Hex={stringify:function(a){for(var e=a.words,a=a.sigBytes,c=[],b=0;b<a;b++){var d=e[b>>>2]>>>24-8*(b%4)&255;c.push((d>>>4).toString(16));c.push((d&15).toString(16))}return c.join(“”)},parse:function(a){for(var e=a.length,c=[],b=0;b<e;b+=2)c[b>>>3]|=parseInt(a.substr(b,2),16)<<24-4*(b%8);return l.create(c,e/2)}},j=n.Latin1={stringify:function(a){for(var e=
a.words,a=a.sigBytes,b=[],d=0;d<a;d++)b.push(String.fromCharCode(e[d>>>2]>>>24-8*(d%4)&255));return b.join(“”)},parse:function(a){for(var b=a.length,c=[],d=0;d<b;d++)c[d>>>2]|=(a.charCodeAt(d)&255)<<24-8*(d%4);return l.create(c,b)}},k=n.Utf8={stringify:function(a){try{return decodeURIComponent(escape(j.stringify(a)))}catch(b){throw Error(“Malformed UTF-8 data”);}},parse:function(a){return j.parse(unescape(encodeURIComponent(a)))}},h=b.BufferedBlockAlgorithm=m.extend({reset:function(){this._data=l.create();
this._nDataBytes=0},_append:function(a){“string”==typeof a&&(a=k.parse(a));this._data.concat(a);this._nDataBytes+=a.sigBytes},_process:function(a){var b=this._data,c=b.words,d=b.sigBytes,j=this.blockSize,h=d/(4*j),h=a?g.ceil(h):g.max((h|0)-this._minBufferSize,0),a=h*j,d=g.min(4*a,d);if(a){for(var f=0;f<a;f+=j)this._doProcessBlock(c,f);f=c.splice(0,a);b.sigBytes-=d}return l.create(f,d)},clone:function(){var a=m.clone.call(this);a._data=this._data.clone();return a},_minBufferSize:0});b.Hasher=h.extend({init:function(){this.reset()},
reset:function(){h.reset.call(this);this._doReset()},update:function(a){this._append(a);this._process();return this},finalize:function(a){a&&this._append(a);this._doFinalize();return this._hash},clone:function(){var a=h.clone.call(this);a._hash=this._hash.clone();return a},blockSize:16,_createHelper:function(a){return function(b,c){return a.create(c).finalize(b)}},_createHmacHelper:function(a){return function(b,c){return u.HMAC.create(a,c).finalize(b)}}});var u=f.algo={};return f}(Math);
(function(){var g=CryptoJS,i=g.lib,f=i.WordArray,i=i.Hasher,b=[],m=g.algo.SHA1=i.extend({_doReset:function(){this._hash=f.create([1732584193,4023233417,2562383102,271733878,3285377520])},_doProcessBlock:function(f,n){for(var d=this._hash.words,j=d[0],k=d[1],h=d[2],g=d[3],a=d[4],e=0;80>e;e++){if(16>e)b[e]=f[n+e]|0;else{var c=b[e-3]^b[e-8]^b[e-14]^b[e-16];b[e]=c<<1|c>>>31}c=(j<<5|j>>>27)+a+b[e];c=20>e?c+((k&h|~k&g)+1518500249):40>e?c+((k^h^g)+1859775393):60>e?c+((k&h|k&g|h&g)-1894007588):c+((k^h^g)-
899497514);a=g;g=h;h=k<<30|k>>>2;k=j;j=c}d[0]=d[0]+j|0;d[1]=d[1]+k|0;d[2]=d[2]+h|0;d[3]=d[3]+g|0;d[4]=d[4]+a|0},_doFinalize:function(){var b=this._data,f=b.words,d=8*this._nDataBytes,j=8*b.sigBytes;f[j>>>5]|=128<<24-j%32;f[(j+64>>>9<<4)+15]=d;b.sigBytes=4*f.length;this._process()}});g.SHA1=i._createHelper(m);g.HmacSHA1=i._createHmacHelper(m)})();
(function(){var g=CryptoJS,i=g.enc.Utf8;g.algo.HMAC=g.lib.Base.extend({init:function(f,b){f=this._hasher=f.create();”string”==typeof b&&(b=i.parse(b));var g=f.blockSize,l=4*g;b.sigBytes>l&&(b=f.finalize(b));for(var n=this._oKey=b.clone(),d=this._iKey=b.clone(),j=n.words,k=d.words,h=0;h<g;h++)j[h]^=1549556828,k[h]^=909522486;n.sigBytes=d.sigBytes=l;this.reset()},reset:function(){var f=this._hasher;f.reset();f.update(this._iKey)},update:function(f){this._hasher.update(f);return this},finalize:function(f){var b=
this._hasher,f=b.finalize(f);b.reset();return b.finalize(this._oKey.clone().concat(f))}})})();
(function(){var g=CryptoJS,i=g.lib,f=i.Base,b=i.WordArray,i=g.algo,m=i.HMAC,l=i.PBKDF2=f.extend({cfg:f.extend({keySize:4,hasher:i.SHA1,iterations:1}),init:function(b){this.cfg=this.cfg.extend(b)},compute:function(f,d){for(var g=this.cfg,k=m.create(g.hasher,f),h=b.create(),i=b.create([1]),a=h.words,e=i.words,c=g.keySize,g=g.iterations;a.length<c;){var l=k.update(d).finalize(i);k.reset();for(var q=l.words,t=q.length,r=l,s=1;s<g;s++){r=k.finalize(r);k.reset();for(var v=r.words,p=0;p<t;p++)q[p]^=v[p]}h.concat(l);
e[0]++}h.sigBytes=4*c;return h}});g.PBKDF2=function(b,d,f){return l.create(f).compute(b,d)}})();

1-2. aes_test.htm

<!DOCTYPE html>
<html>
<head>
<title>AES128 Example</title>
<meta charset=”UTF-8″>
<meta name=”viewport” content=”width=device-width, user-scalable=no” />
<link rel=”stylesheet” href=”/css/base.css”>
<script type=”text/javascript” src=”aes_util.js”></script>
<script type=”text/javascript”>

var g_keySize = 128;
var g_iterationCount = 10000;
var g_salt = “79752f1d3fd2432043c48e45b35b24645eb826a25c6f1804e9152665c345a552”;
var g_iv = “2fad5a477d13ecda7f718fbd8a9f0443”;
var g_passPhrase = “passPhrase”;

window.onload = function() {
    
    try {
        var resultDiv = document.getElementById(“resultDiv”);
        
        var aesUtil = new AesUtil(g_keySize, g_iterationCount);
        var encString = aesUtil.encrypt(g_salt, g_iv, g_passPhrase, “pass1234”);
        var decString = aesUtil.decrypt(g_salt, g_iv, g_passPhrase, encString);
        
        resultDiv.innerHTML = “encString : “ + encString + “<br>” + “decString : “ + decString;

    } catch (e) {
        alert(e);
        return false;
    }

}
</script>
</head>
<body>
 AES128 Example
 <br>
 <div id=”resultDiv”></div>
</body>
</html>

aes_util.js 와 aes_test.htm은 압축해서 첨부파일(javascript_aes_util.zip)로 올려두었다.

2. 실행결과

[JAVA] AES128 암호화 예제 바로가기 => https://blog.naver.com/bb_/221332286531

[JAVA/JS] AES128 암호화 예제 github 소스 바로 가기 => https://github.com/thkmon/AES128Test

[JAVA] AES128 암호화 예제

[JAVA] AES128 암호화 예제

[javascript] AES128 암호화 예제 바로가기 => https://blog.naver.com/bb_/221332298450

[JAVA/JS] AES128 암호화 예제 github 소스 바로 가기 => https://github.com/thkmon/AES128Test

AES128은 양방향 암호화를 지원한다. 즉, 키값을 안다면, 암호화한 스트링을 다시 복호화할 수 있다. 키값을 모를 경우, 복호화가 거의 불가능한만큼 안전하다고 알려져 있다.

1. 라이브러리 임포트

commons-codec-1.10.jar 임포트가 필요하다. 첨부파일로 첨부해두었다.

스프링에 적용하고 싶다면 pom.xml 에 아래와 같이 추가하면 된다.

<!– https://mvnrepository.com/artifact/commons-codec/commons-codec –>
<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
</dependency>

1-1. 임포트시 주의사항

노파심에 적어두지만, Base64 는 java.util.Base64 가 아니라 org.apache.commons.codec.binary.Base64 를 임포트해야 한다.

2. 소스코드

2-1. [AesUtil.java]

package com.bb;

import java.security.SecureRandom;
import java.security.spec.KeySpec;

import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;
import org.apache.commons.codec.binary.Hex;

public class AesUtil {

    private static final int keySize = 128;
    private static final int iterationCount = 10000;
    private static String salt = “79752f1d3fd2432043c48e45b35b24645eb826a25c6f1804e9152665c345a552”;
    private static String iv = “2fad5a477d13ecda7f718fbd8a9f0443”;
    private static final String passPhrase = “passPhrase”;
    
    private final Cipher cipher;
    
    
    public AesUtil() {
        try {
            cipher = Cipher.getInstance(“AES/CBC/PKCS5Padding”);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    
    public String encrypt(String plaintext) throws Exception {
        return encrypt(salt, iv, passPhrase, plaintext);
    }
    
    
    public String decrypt(String ciphertext) throws Exception {
        return decrypt(salt, iv, passPhrase, ciphertext);
    }
    
    
    private String encrypt(String salt, String iv, String passPhrase, String plaintext) throws Exception {
        SecretKey key = generateKey(salt, passPhrase);
        byte[] encrypted = doFinal(Cipher.ENCRYPT_MODE, key, iv, plaintext.getBytes(“UTF-8”));
        return encodeBase64(encrypted);
    }

    
    private String decrypt(String salt, String iv, String passPhrase, String ciphertext) throws Exception {
        SecretKey key = generateKey(salt, passPhrase);
        byte[] decrypted = doFinal(Cipher.DECRYPT_MODE, key, iv, decodeBase64(ciphertext));
        return new String(decrypted, “UTF-8”);
    }

    
    private byte[] doFinal(int encryptMode, SecretKey key, String iv, byte[] bytes) throws Exception {
        cipher.init(encryptMode, key, new IvParameterSpec(decodeHex(iv)));
        return cipher.doFinal(bytes);
    }

    
    private SecretKey generateKey(String salt, String passPhrase) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(“PBKDF2WithHmacSHA1”);
        KeySpec spec = new PBEKeySpec(passPhrase.toCharArray(), decodeHex(salt), iterationCount, keySize);
        SecretKey key = new SecretKeySpec(factory.generateSecret(spec).getEncoded(), “AES”);
        return key;
    }
    
    
    private static String encodeBase64(byte[] bytes) {
        return Base64.encodeBase64String(bytes);
    }

    
    private static byte[] decodeBase64(String str) {
        return Base64.decodeBase64(str);
    }

    
    private static String encodeHex(byte[] bytes) {
        return Hex.encodeHexString(bytes);
    }

    
    private static byte[] decodeHex(String str) throws Exception {
        return Hex.decodeHex(str.toCharArray());
    }
    
    
    private static String getRandomHexString(int length) {
        byte[] salt = new byte[length];
        new SecureRandom().nextBytes(salt);
        return encodeHex(salt);

    }
}

– keySize : AES128 의 128을 의미한다.

– iterationCount : 암호화 관련키(PEBKey)을 생성하는 횟수를 의미한다. 10000 번은 기본값에 해당되고 횟수가 높을수록 암호화 강도가 강하다고 한다.

– salt : 해시값의 무작위성을 높이기 위한 보조키에 해당한다. 실제로 해시값에 소금을 친다(Adding Salt)는 표현을 쓴다.

– iv : AES암호화를 위한 메인키라고 볼 수 있다.

현재 salt값과 iv값은 정적변수(static)로 되어 있지만 실제 사용시에는 그때그때 생성해서 사용해도 된다.

salt는 32자리의 헥스값, iv는 16자리의 헥스값을 만들면 된다.

아래처럼 랜덤 헥스값을 만드는 메서드 getRandomHexString 를 사용하면 된다.

salt = getRandomHexString(32);
iv = getRandomHexString(16);

2-2. [AES128Test.java]

package com.bb;

public class AES128Test {

    public static void main(String[] args) {
        try {
            AesUtil aesUtil = new AesUtil();
            
            String encString = aesUtil.encrypt(“pass1234”);
            System.out.println(“encString : “ + encString);
            
            String decString = aesUtil.decrypt(encString);
            System.out.println(“decString : “ + decString);
            
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

3. 실행결과

[javascript] AES128 암호화 예제 바로가기 => https://blog.naver.com/bb_/221332298450

[JAVA/JS] AES128 암호화 예제 github 소스 바로 가기 => https://github.com/thkmon/AES128Test 

[JAVA] java MD5 / java SHA-256

[JAVA] java MD5 / java SHA-256

    public String convertToMD5(String str) throws Exception {
        return convertToType(str, “MD5”);
    }
    
    
    public String convertToSHA256(String str) throws Exception {
        return convertToType(str, “SHA-256”);
    }
    
    
    private String convertToType(String str, String type) throws Exception {
        if (str == null || str.length() == 0) {
            new Exception(“convertToType : str is null or empty.”);
        }
        
        if (type == null || type.length() == 0) {
            type = “SHA-256”;
        }
        
        String result = “”;
        StringBuffer buff = new StringBuffer();
        MessageDigest digest = null;
        
        try {
            digest = MessageDigest.getInstance(type);
            byte[] hash = digest.digest(str.getBytes(“UTF-8”));
            int len = hash.length;

            for (int i=0 ; i<len; i++) {
                String hex = Integer.toHexString(0xff & hash[i]);
                if (hex.length() == 1) {
                    buff.append(“0”);
                }

                buff.append(hex);
            }

            result = buff.toString();

        } catch(Exception e) {
            throw e;
        }

        return result;
    }

Tomcat7에 스프링4 프로젝트 배포

Tomcat7에 스프링4 프로젝트 배포

1. Tomcat7 설치 및 기동성공

먼저 Tomcat7 설치 및 기동에 성공해야 한다.

Tomcat7 설치 방법 : https://blog.naver.com/bb_/221330415361

​2. 프로젝트 Export (war 파일 형태)
Spring4 프로젝트를 wer 파일 형태로 Export 해야 한다.
이클립스(또는 STS) 상단 메뉴의 [File] – [Export] – Web 폴더의 [WAR file] 선택하고 [Next] 버튼 클릭 – Destination 설정하고 [Finish] 버튼 클릭 – 파일이름 ROOT.war 로 저장
3. server.xml 수정
톰캣 폴더/conf 폴더 내의 server.xml을 수정한다.
appBase=”webapps” 를 appBase=”webapp” 으로 수정한다.
4. 톰캣경로/webapp/ROOT 폴더 생성, ROOT.war 업로드
톰캣폴더 내에 webapp 폴더를 생성한다.
ex)
cd /home/spring4/tomcat7
mkdir webapp
webapp 폴더 안에 ROOT 폴더를 생성한다.
ex)
cd /home/spring4/tomcat7/webapp
mkdir ROOT
FTP를 이용하여 webapp 폴더 안에 ROOT.war 를 업로드한다.
예를 들어, 경로는 /home/spring4/tomcat7/webapp/ROOT.war 가 된다.
5. 톰캣 기동
톰캣을 기동한다.
ex)
cd /home/spring4/tomcat7/bin
./startup.sh
만약 권한 에러가 뜨면 chown 을 이용해서 권한을 주거나, 애초에 FTP로 업로드할 때 해당 사용자로 로그인해서 업로드 하면 된다.
ex)
cd /home/spring4/tomcat7/webapp
chown -R spring4:spring4 *
도메인:포트번호 에 접속했을 때 개발한 스프링 프로젝트가 뜨면 성공이다.

ubuntu에 tomcat7 설치 (톰캣 2개 띄우기)

ubuntu에 tomcat7 설치 (톰캣 2개 띄우기)

우분투에 톰캣7 설치하기를 해보았다.

이미 톰캣8을 설치했던 서버이기 때문에, 지난번과 조금 차이점이 있었고 기록으로 남긴다.

결과적으로 톰캣 2대 기동하기 실습이 됐다.

톰캣 2개를 띄우기 위한 핵심은 아래 두 가지다.

1. 카탈리나 홈이 서로 다른 user가 톰캣 기동해야 함 (.profile 수정 필요)

2. 포트 다르게 설정 (server.xml 수정 필요)


◆ 첫번째 톰캣 띄우기

먼저 톰캣 하나를 설치하려면 아래 게시글을 보면 된다.

톰캣8이지만 7과 그다지 다를 바 없다.

우분투에서 tomcat8로 자바 프로젝트 띄우기 1/2 : 톰캣을 띄워보자

http://blog.naver.com/bb_/220907952848

◆ 두번째 톰캣 띄우기

1. 리눅스 설치 / 자바 JDK 설치와 환경변수 설정

생략한다.

2. 사용자 생성

adduser spring4

먼저 기존 톰캣과 다른 홈(카탈리나 홈)을 사용하기 위해서, 사용자를 새로 생성하였다.

비밀번호만 신경써서 입력하고, 나머지 값은 1, 2, 3, 4, 5 등 적당히 입력했더니,

/home/spring4 를 홈폴더로 하는 사용자가 생성되었다.

3. 톰캣7 다운로드

http://tomcat.apache.org/ 에서 톰캣 7을 다운받는다.

4. FTP 업로드

다운받은 apache-tomcat-7.0.90.tar.gz 를 /home/spring4 위치에 FTP로 업로드한다.

root로 접속후 파일에 권한을 부여한다.

su – root

cd /home/spring4

chown spring4:spring4 *

chmod -R 755 *

압축을 푼다.

tar -xvf apache-tomcat-7.0.90.tar.gz

폴더명을 보기 쉽게 바꾼다.

mv apache-tomcat-7.0.90 tomcat7

5. 카탈리나 홈 설정

카탈리나 홈을 다르게 설정해야 한다.

홈 폴더에서 profile 파일을 수정한다.

cd /home/spring4

vi .profile

파일 하단에

JAVA_HOME=/usr
CATALINA_HOME=/home/spring4/tomcat7
CLASSPATH=$JAVA_HOME/lib/tools.jar:$CATALINA_HOME/lib/jsp-api.jar:$CATALINA_HOME/lib/servlet-api.jar
PATH=$PATH:$JAVA_HOME/bin:$CATALINA_HOME/bin
export JAVA_HOME  CLASSPATH PATH CATALINA_HOME

라고 내용 작성하고 :wq! 한다.

6. 포트 다르게 설정 (server.xml 수정)

기존 tomcat과 포트를 다르게 설정해야 한다.

cd /home/spring4/tomcat7/conf

vi server.xml

어렵게 바꾸면 헷갈리니까 무조건 8을 7로 바꾼다는 생각으로 수정했다.

<Server port=”8005″
<Server port=”7005″

<Connector port=”8080″
<Connector port=”7070″ 으로

redirectPort=”8443″
redirectPort=”7443″ 으로

<Connector port=”8009″ protocol=”AJP/1.3″ redirectPort=”8443″ />
<Connector port=”7009″ protocol=”AJP/1.3″ redirectPort=”7443″ /> 으로

이렇게 하니 쉽게 바꿀 수 있었다.

포트를 제대로 바꾸지 않으면 한쪽을 shutdown할 때 다른 한 쪽도 같이 shutdown 된다.

이러한 경우 포트를 다시 확인해보자.

기본 세팅으로 톰캣을 설치했다면,

첫번째 톰캣은 도메인:8080  또는 도메인:80 으로, 두번째 톰캣은 도메인:7070 으로 접속할 수 있다.

7. 톰캣 기동

기동은

/home/spring4/tomcat7/bin 에서 ./startup.sh

기동중지는

/home/spring4/tomcat7/bin 에서 ./shutdown.sh

로그 조회는

/home/spring4/tomcat7/logs 에서 tail -f catalina.out

하면 된다. 기동하기 전에 tail 미리 걸어놓고 보면 된다.

8. 결과 스크린샷

7070포트로 접속하면 톰캣이 잘 나온다.

이상 우분투에서 톰캣7 설치하기 였다.

<엑셀VBA 입문 15강> 텍스트 파일쓰기, 파일읽기

<엑셀VBA 입문 15강> 텍스트 파일쓰기, 파일읽기

엑셀 VBA로 텍스트 파일쓰기, 텍스트 파일읽기를 배워본다.

1. VBA 텍스트 파일쓰기

Sub 매크로1()

‘ 매크로1 매크로

‘ 바로 가기 키: Ctrl+k

Call writeTextFile

End Sub

Function writeTextFile()
    
    On Error GoTo exmsg
    
    MsgBox (“파일쓰기 시작”)
    
    ‘파일 경로 지정
    Dim path
    path = “C:\test\test.txt”
    

    Open path For Append As #1 일 경우 이어쓰기
    Open path For Output As #1
        Print #1, “첫번째 라인”
        Print #1, “두번째 라인”
        Print #1, “세번째 라인”
    Close #1
    
    MsgBox (“파일쓰기 끝”)
    Exit Function
    
exmsg:
    Close #1
    MsgBox (“Error” & Err.Number & ” : ” & Err.Description)

End Function

파일을 쓰기 위한 가장 간단한 코드다.

C드라이브의 test 폴더 내의 test.txt 파일에 “첫번째 라인”, “두번째 라인”, “세번째 라인”, 이렇게 3줄의 텍스트를 기록하고 저장한다.

여기서 #1 이란 파일스트림이다. 파일스트림이란, 파일을 읽거나 쓰기 위한 어떤 구조다. 스트림은 원래 물줄기라는 뜻이지만, 그냥 스트림이라고 하면 긴 호스나 빨대를 생각해도 좋다.

[Open path For Output As #1] 란 path경로의 파일을 #1[샾1] 이란 명칭의 아웃풋 스트림으로 연다는 뜻이다.

1-1. test 폴더 생성 필요

C드라이브에 test 폴더를 생성 후 실행해야 한다.

그렇지 않으면 [경로를 찾을 수 없습니다] 에러가 발생할 수 있다.

1-2.  파일 이어서 쓰기

파일을 이어서 쓰려면 [Open path For Output As #1] 의 Output을 Append로 고치면 된다. 즉, [Open path For Append As #1]하면 파일 이어서 쓰기가 된다.

예를 들어 For Output 로 writeTextFile 함수를 여러 번 수행하면, 몇 번을 수행하든 결과는 3줄짜리 텍스트 파일이다.

그런데 For Append 로 writeTextFile 함수를 여러 번 수행하면, 아래와 같이 텍스트 파일 뒤에 내용이 계속 추가된다.

2. VBA 텍스트 파일읽기

Sub 매크로1()

‘ 매크로1 매크로

‘ 바로 가기 키: Ctrl+k

Call readTextFile

End Sub

Function readTextFile()
    
    On Error GoTo exmsg
    
    MsgBox (“파일읽기 시작”)
    
    ‘파일 경로 지정
    Dim path
    path = “C:\test\test.txt”
    
    Dim lineNum
    lineNum = 1
    
    Dim lineValue As String

    Open path For Input As #1
        Do While Not EOF(1)
            Line Input #1, lineValue
            Cells(lineNum, 1).Value = lineValue
            lineNum = lineNum + 1
        Loop
    Close #1
    
    MsgBox (“파일읽기 끝”)
    Exit Function
    
exmsg:
    Close #1
    MsgBox (“Error” & Err.Number & ” : ” & Err.Description)

End Function

위 코드는 텍스트 파일을 읽어 Cells(1,1) – 즉 A1 셀부터 한 줄씩 내용을 출력하는 코드다.

[Do While Not EOF(1)]라는 부분이 있는데, 여기서 1은 파일스트림의 번호를 뜻한다. EOF란 End Of File의 약자로, 파일의 끝을 의미한다.

파일의 끝일 경우, EOF는 true를 리턴한다. 파일의 끝이 아닐 경우, EOF는 false를 리턴한다.

즉 [Do While Not EOF(1)] 는 파일스트림 1이 끝나지 않는 한 계속 도는 루프문이다.

위 코드를 실행하면 아래와 같은 결과를 얻는다.

만약 텍스트 파일의 형식이 UTF-8 이라면 다음 소스코드를 사용하시면 됩니다.

Const CP_UTF8 = 65001
Private Declare Function MultiByteToWideChar Lib “kernel32” (ByVal CodePage As Long, ByVal dwFlags As Long, ByVal lpMultiByteStr As Long, ByVal cchMultiByte As Long, ByVal lpWideCharStr As Long, ByVal cchWideChar As Long) As Long


Sub 매크로1()

‘ 매크로1 매크로

‘ 바로 가기 키: Ctrl+k

Call readTextFile

End Sub

Function readTextFile()
    
    On Error GoTo exmsg
    
    MsgBox (“파일읽기 시작”)
    
    ‘파일 경로 지정
    Dim path
    path = “C:\test\test.txt”
    
    Dim utf8() As Byte
    Dim ucs2 As String
    Dim chars As Long
   
    Open path For Binary As #1
    ReDim utf8(LOF(1))
   
    Get #1, , utf8
   
    chars = MultiByteToWideChar(CP_UTF8, 0, VarPtr(utf8(0)), LOF(1), 0, 0)
    ucs2 = Space(chars)
   
    chars = MultiByteToWideChar(CP_UTF8, 0, VarPtr(utf8(0)), LOF(1), StrPtr(ucs2), chars)
    
    Close
    

    ‘엔터를 구분자로 Split 하기
    Dim tArray
    tArray = Split(ucs2 + “”, vbCrLf, , vbTextCompare)
   
    ‘파일 내용 표시
    For i = 0 To UBound(tArray)
        Cells(i + 1, 1).Value = tArray(i) + “”
    Next i

   
    MsgBox (“파일읽기 끝”)
    Exit Function
    
exmsg:
    Close #1
    MsgBox (“Error” & Err.Number & ” : ” & Err.Description)

End Function

이어지는 글 <엑셀VBA 입문 16강> VBA로 워크시트 함수 활용하기 : https://blog.naver.com/bb_/221350410698

이어지는 글 <엑셀VBA 입문 17강> 변수를 같은 이름으로 사용하면 섞일까? (지역변수와 전역변수) :

https://blog.naver.com/bb_/221417490850

이어지는 글 <엑셀VBA 입문 18강> indexOf 함수의 확장 https://blog.naver.com/bb_/221650929713

<엑셀VBA 입문 14강> On Error 구문의 사용

<엑셀VBA 입문 14강> On Error 구문의 사용

VBA 코드를 찾다보면 “On Error Resume Next” 라던지 “On Error GoTo 레이블명”과 같은 명령어를 본 일이 있을 것이다. (본 적 없어도 괜찮다.)

오늘은 이 On Error의 정체에 대해서 배운다.

간단히 설명하면 On Error 라는 구문은 <에러가 발생했을 경우 처리방식>을 결정한다.

1. 에러가 나는 함수

먼저 에러가 나는 상황을 일부러 만들어보자.

아래와 같이 doTest 함수를 작성한다.

Sub 매크로1()

‘ 매크로1 매크로
‘ 바로 가기 키: Ctrl+k

    Call doTest

End Sub

Function doTest()

    Dim a
    a = 10 / 0

    MsgBox (“함수의 끝”)

End Function

보다시피 굉장히 간단한 코드다. 단축키 Ctrl + k 를 누르면 doTest 함수가 실행되지만, 에러가 나는 경우다.

산수에서 어떤 숫자를 0 으로 나누는 것은 불가능하다고 되어 있는데, 프로그래밍에서도 똑같다.

숫자를 0 으로 나누면 에러가 난다. (아래 그림)

“0으로 나누었습니다”라는 에러가 발생하였다. 당연하게도 그 아래 MsgBox 명령어는 아예 수행되지 않았다.

프로그램 수행이 중단된 것이다.

하지만 어떠한 경우에도 프로그램이 중단되지 않고 수행되기를 원한다면 어떨까?

예를 들면 프로그램을 돌려놓고 10시간 쯤 자리를 비우는 경우 말이다.

실제로 필자는 VBA로 특정 업무를 자동화해놓고, 다른 업무를 진행했던 적이 종종 있다.

2. 에러가 발생해도 무시하기 (On Error Resume Next)

On Error Resume Next 는 함수에 에러가 발생해도 현상을 무시한다.

함수 첫번째 줄에 쓰면 된다.

이를 테면 아래와 같은 코드다.

Function doTest()

    On Error Resume Next
    Dim a
    a = 10 / 0

    MsgBox (“함수의 끝”)

End Function

3. 에러가 발생하면 임의처리하기 (On Error GoTo 레이블명)

다음으로 On Error GoTo 구문이 있다. 사용은 [On Error GoTo 레이블명] 으로 쓴다. 예를 들면 [On Error GoTo aaaaa] 같은 식이다.

레이블이란 특정한 위치를 뜻하는데, [레이블명:] 형식으로 쓰면 된다.

[레이블명:] 이라고 써두면 나중에 GoTo문으로 도달할 수 있는 지점이 된다.

아래 예제를 보면 쉽게 이해될 것이다.

Function doTest()
 
    ‘에러 발생시 aaaaa 레이블로 이동(GoTo)
    On Error GoTo aaaaa

    Dim a
    a = 10 / 0
        
    ‘함수의 끝
    Exit Function 

aaaaa:
    MsgBox (“에러가 났어요!”)
    
End Function

여기서 중요한 것은 레이블 aaaaa 위쪽에 [Exit Function] 이란 명령어를 적어서 함수를 종료시켜주는 것이다.

[Exit Function] 이라는 명령어를 적지 않으면, 함수는 첫번째 줄부터 마지막 줄까지 진행되기 때문에, 에러가 발생하지 않았을 경우에도 [에러가 났어요]라는 메시지가 발생하게 된다.

참고로 지금은 Function 안쪽이기 때문에 [Exit Function] 명령어를 사용했지만, Sub 안쪽이라면 [Exit Sub] 명령어를 사용해야 한다.

즉, aaaaa: 라는 명령어는 어떤 지점을 표시해두는 기능 뿐이다. 레이블을 지정한다고 해서 독립된 공간이 마련되는 것은 아니다.

특히 에러가 발생했을 때 메시지를 띄워줘야 한다면, 실제로 [에러가 났어요]같은 애매한 메시지보다는, 정확한 메시지를 띄워주는 편이 낫다. 아래 코드를 사용하면 된다.

MsgBox  (“에러코드 ” & Err.Number & ” 발생 : ” & Err.Description)

일반적으로 어떤 함수인가에 따라 에러 처리를 다르게 해주는 편이 좋다.

예를 들어 문자열을 잘라내는 함수라면 에러 발생시 빈 값을 리턴하도록 처리해주는게 좋고,

숫자를 리턴하는 함수라면 0 이나 -1 을 리턴해서 에러가 발생해도 문제 없이 진행되도록 처리하는 편이 좋다.

에러가 났다고 명시적으로 메시지를 꼭 띄워줘야 하는 상황이라면, MsgBox를 띄워주자.

이상 VBA On Error 구문의 사용이었다.

이어지는 글 <엑셀VBA 입문 15강> 텍스트 파일쓰기, 파일읽기 : https://blog.naver.com/bb_/221329757129
이어지는 글 <엑셀VBA 입문 16강> VBA로 워크시트 함수 활용하기 : https://blog.naver.com/bb_/221350410698

이어지는 글 <엑셀VBA 입문 17강> 변수를 같은 이름으로 사용하면 섞일까? (지역변수와 전역변수) :

https://blog.naver.com/bb_/221417490850

[JAVA] 자바 PDF 변환 (pdf를 jpg로 변환 / jpg를 pdf로 변환)

[JAVA] 자바 PDF 변환 (pdf를 jpg로 변환 / jpg를 pdf로 변환)

자바에서 PDF를 다루기 위한 라이브러리로 apache 의 pdfbox 가 있다.

본 게시글에서는 pdfbox 2.0.11 버전을 사용하였다.

우선 http://pdfbox.apache.org/ 에 들어가서 pdfbox-app-2.0.11.jar 를 다운받는다. (아래 그림)

잘 다운로드 되었으면 활용할 프로젝트 클래스 패스에 추가하자.

1. PDF 파일을 JPG 파일들로 변환하는 방법

PDF 파일을 JPG 파일들로 변환하는 방법이다.

try ~ catch가 포함된 자세한 코드는 본 게시글의 “convertPDFFileToJPGFile” 메서드를 검색해 보면 된다.

1-1. PDF 문서 객체(PDDocument) 준비

File file = new File(“pdf파일의위치”);
PDDocument document = PDDocument.load(file);

1-2. PDF 파일의 페이지 수 가져오기

int pageCount = document.getNumberOfPages();

1-3. PDF 파일을 JPG 파일로 변환

PDFRenderer pdfRenderer = new PDFRenderer(document);

// 0 페이지를 JPG파일로 저장
BufferedImage imageObj = pdfRenderer.renderImageWithDPI(0, 100, ImageType.RGB);
File outputfile = new File(“저장할jpg파일경로지정”);
ImageIO.write(imageObj, “jpg”, outputfile);

// 1 페이지를 JPG파일로 저장
BufferedImage imageObj = pdfRenderer.renderImageWithDPI(1, 100, ImageType.RGB);
File outputfile = new File(“저장할jpg파일경로지정”);
ImageIO.write(imageObj, “jpg”, outputfile);

// 2 페이지를 JPG파일로 저장
BufferedImage imageObj = pdfRenderer.renderImageWithDPI(2, 100, ImageType.RGB);
File outputfile = new File(“저장할jpg파일경로지정”);
ImageIO.write(imageObj, “jpg”, outputfile);

… (후략)

1-4. 사용한 PDF 문서 객체 닫기

if (document != null) {
    document.close();
}

2. JPG 파일들을 PDF 파일로 변환하는 방법

JPG 파일들을 PDF 파일들로 변환하는 방법이다.

try ~ catch가 포함된 자세한 코드는 본 게시글의 “convertJPGFileToPDFFile” 메서드를 검색해 보면 된다.

2-1. 새 PDF 문서 객체(PDDocument) 준비

PDDocument document = new PDDocument();

2-2. JPG 파일 읽어서 PDF 문서 객체에 1장씩 그리기

// 추가할 JPG 파일 읽기
File oneFile = new File(“추가할JPG파일경로지정”);
InputStream inputStream = new FileInputStream(oneFile);
BufferedImage bufferedImage = ImageIO.read(inputStream);
float width = bufferedImage.getWidth();
float height = bufferedImage.getHeight();

// PDF 페이지 객체 1장 생성
PDPage page = new PDPage(new PDRectangle(width, height));
document.addPage(page);

// PDF 문서 객체에 페이지 1장 그리기
PDImageXObject pdImage = PDImageXObject.createFromFile(oneFile.getAbsolutePath(), document);
PDPageContentStream contentStream = new PDPageContentStream(document, page);
contentStream.drawImage(pdImage, 0, 0, width, height);

// 1장 그릴 때마다 사용한 객체 닫기

if (contentStream != null) {
    contentStream.close();
}

if (inputStream != null) {
    inputStream.close();
}

2-3. PDF 문서를 파일로 저장 

document.save(“결과PDF파일경로지정”);

2-4. 사용한 PDF 문서 객체 닫기

if (document != null) {
    document.close();
}

아래부터 직접 작성한 예제 소스코드이며 http://github.com/thkmon/PDFConverter 에서 자세한 내용을 볼 수 있다.

———-

package com.bb;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;

import javax.imageio.ImageIO;

import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.PDPage;
import org.apache.pdfbox.pdmodel.PDPageContentStream;
import org.apache.pdfbox.pdmodel.common.PDRectangle;
import org.apache.pdfbox.pdmodel.graphics.image.PDImageXObject;
import org.apache.pdfbox.rendering.ImageType;
import org.apache.pdfbox.rendering.PDFRenderer;

public class PDFConverter {

    
    public static void main(String[] args) {
        
        PDFConverter pdfConverter = null;
        
        try {
            pdfConverter = new PDFConverter();
            pdfConverter.testConvertPDF2JPG();
            pdfConverter.testConvertJPG2PDF();
        
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    
    /**
     * example method of converting PDF file to JPG file.
     *
     * @throws Exception
     */

    public void testConvertPDF2JPG() throws Exception {
        String pdfFilePath = new File(“input/example.pdf”).getAbsolutePath();
        String destFolderPath = new File(“output”).getAbsolutePath();
        this.convertPDFFileToJPGFile(pdfFilePath, destFolderPath);
    }
    
    
    /**
     * example method of converting JPG file to PDF file.
     *
     * @throws Exception
     */

    public void testConvertJPG2PDF() throws Exception {
        String jpgsFolderPath = new File(“output”).getAbsolutePath();
        String destFolderPath = new File(“result/result.pdf”).getAbsolutePath();
        this.convertJPGFileToPDFFile(jpgsFolderPath, destFolderPath);
    }
    
    
    /**
     * convert PDF file to JPG file.
     *
     * @param pdfFilePath
     * @param destFolderPath
     * @throws Exception
     */

    public void convertPDFFileToJPGFile(String pdfFilePath, String destFolderPath) throws Exception {
        
        System.out.println(“convertPDFFileToJPGFile : begin”);
        
        if (pdfFilePath == null || pdfFilePath.length() == 0) {
            throw new Exception(“convertPDFFileToJPGFile : pdfFilePath is null or empty”);
        }
        
        if (destFolderPath == null || destFolderPath.length() == 0) {
            throw new Exception(“convertPDFFileToJPGFile : destFolderPath is null or empty”);
        }
        
        PDDocument document = null;
        
        try {
            File file = new File(pdfFilePath);
            if (!file.exists()) {
                throw new Exception(“convertJPGFileToPDFFile : this path not exists. [“ + file.getAbsolutePath() + “]”);
            }
            
            String fileName = file.getName();
            if (fileName == null || fileName.length() == 0) {
                throw new Exception(“convertJPGFileToPDFFile : fileName is null or empty. [“ + file.getAbsolutePath() + “]”);
            }
            
            String fileNameOnly = null;
            
            int dotIndex = fileName.lastIndexOf(“.”);
            if (dotIndex > -1) {
                fileNameOnly = fileName.substring(0, dotIndex);
            } else {
                fileNameOnly = fileName;
            }
            
            if (fileNameOnly.indexOf(“.”) > -1) {
                fileNameOnly = fileNameOnly.replace(“.”, “_”);
            }
            
            File destFolder = new File(destFolderPath);
            if (!destFolder.exists()) {
                destFolder.mkdirs();
            }
            
            document = PDDocument.load(file);
            
            int pageCount = document.getNumberOfPages();
            System.out.println(“pageCount : “ + pageCount);
            
            PDFRenderer pdfRenderer = new PDFRenderer(document);
            for (int i=0; i<pageCount; i++) {
                int pageNum = i + 1;
                
             BufferedImage imageObj = pdfRenderer.renderImageWithDPI(i, 100, ImageType.RGB);
            
             String imageFileName = fileNameOnly + “-“ + pageNum + “.jpg”;
            
             File outputfile = new File(destFolderPath + “/” + imageFileName);
             ImageIO.write(imageObj, “jpg”, outputfile);
            
             System.out.println(“output “ + pageNum + “/” + pageCount + ” : “ + outputfile.getAbsolutePath());
            }
        
        } catch (Exception e) {
            throw e;
            
        } finally {
            try {
                if (document != null) {
                    document.close();
                }
            } catch (Exception e) {}
        }
        
        System.out.println(“convertPDFFileToJPGFile : end”);
    }
    
    
    /**
     * convert JPG file to PDF file.
     *
     * @param jpgsFolderPath
     * @param resultFilePath
     * @throws Exception
     */

    public void convertJPGFileToPDFFile(String jpgsFolderPath, String resultFilePath) throws Exception {
        
        System.out.println(“convertPDFFileToJPGFile : begin”);
        
        if (jpgsFolderPath == null || jpgsFolderPath.length() == 0) {
            throw new Exception(“convertPDFFileToJPGFile : jpgsFolderPath is null or empty”);
        }
        
        if (resultFilePath == null || resultFilePath.length() == 0) {
            throw new Exception(“convertPDFFileToJPGFile : resultFilePath is null or empty”);
        }
        
        PDDocument document = null;
        
        try {
            File folder = new File(jpgsFolderPath);
            if (!folder.exists()) {
                folder.mkdirs();
            }
            
            if (!folder.isDirectory()) {
                throw new Exception(“convertJPGFileToPDFFile : this path is not a folder. [“ + folder.getAbsolutePath() + “]”);
            }
            
            File[] fileArr = folder.listFiles();
            int fileCount = 0;
            if (fileArr != null) {
                fileCount = fileArr.length;
            }
            
            if (fileCount == 0) {
                throw new Exception(“convertJPGFileToPDFFile : fileCount == 0”);
            }
            
            sortFileArray(fileArr);
            
            document = new PDDocument();
            
            for (int i=0; i<fileCount; i++) {
                File oneFile = fileArr[i];
                
                if (oneFile == null || !oneFile.exists()) {
                    continue;
                }
                
                InputStream inputStream = null;
                PDPageContentStream contentStream = null;
                
                try {
                    inputStream = new FileInputStream(oneFile);
                    BufferedImage bufferedImage = ImageIO.read(inputStream);
                    float width = bufferedImage.getWidth();
                    float height = bufferedImage.getHeight();
                    
                    PDPage page = new PDPage(new PDRectangle(width, height));
                    document.addPage(page);
    
                    PDImageXObject pdImage = PDImageXObject.createFromFile(oneFile.getAbsolutePath(), document);
                    contentStream = new PDPageContentStream(document, page);
//                    contentStream.drawImage(pdImage, 0, 0);
                    contentStream.drawImage(pdImage, 0, 0, width, height);
                    
                } catch (Exception e) {
                    throw e;
                    
                } finally {
                    try {
                        if (contentStream != null) {
                            contentStream.close();
                        }
                    } catch (Exception e) {}
                    
                    try {
                        if (inputStream != null) {
                            inputStream.close();
                        }
                    } catch (Exception e) {}
                }
            }
            
            File resultFile = new File(resultFilePath);
            if (resultFile.exists()) {
                resultFile.delete();
            }
            
            String motherFolderPath = getMotherFolderPath(resultFile);
            File motherFolder = new File(motherFolderPath);
            if (!motherFolder.exists()) {
                motherFolder.mkdirs();
            }
            
            document.save(resultFile.getAbsolutePath());
            
        } catch (Exception e) {
            throw e;
            
        } finally {
            try {
                if (document != null) {
                    document.close();
                }
            } catch (Exception e) {}
        }
        
        System.out.println(“convertPDFFileToJPGFile : end”);
    }
    
    
    private String getMotherFolderPath(File fileObj) throws Exception {
        
        if (fileObj == null) {
            return “”;
        }
        
        String path = revisePath(fileObj.getAbsolutePath());
        int lastSlashIdx = path.lastIndexOf(“/”);
        if (lastSlashIdx < 0) {
            throw new Exception(“getMotherFolderPath : this path is not valid. [“ + path + “]”);
        }
        
        String motherFolderPath = path.substring(0, lastSlashIdx);
        return motherFolderPath;
    }
    
    
    private String revisePath(String path) {
        if (path == null || path.length() == 0) {
            return “”;
        }
        
        if (path.indexOf(“\\”) > -1) {
            path = path.replace(“
\\”, “/”);
        }
        
        while (path.indexOf(//”) > -1) {
            path = path.replace(//”, “/”);
        }
        
        return path.trim();
    }
    
    
    private void sortFileArray(File[] fileArr) throws Exception {
        if (fileArr == null) {
            return;
        }
        
        if (fileArr.length < 2) {
            return;
        }
        
        File file1 = null;
        File file2 = null;
        File tempFile = null;
        
        int fileCount = fileArr.length;
        for (int i=0; i<fileCount; i++) {
            for (int k=i+1; k<fileCount; k++) {
                file1 = fileArr[i];
                file2 = fileArr[k];
                
                if (checkShouldChangeOrder(file1.getName(), file2.getName())) {
                    tempFile = fileArr[i];
                    fileArr[i] = file2;
                    fileArr[k] = tempFile;
                }
            }
        }
    }
    
    
    private boolean checkShouldChangeOrder(String firstStr, String nextStr) {
        if (firstStr == null) {
            firstStr = “”;
        }
        
        if (nextStr == null) {
            nextStr = “”;
        }
        
        if (firstStr.length() > nextStr.length()) {
            return true;
            
        } else if (firstStr.length() < nextStr.length()) {
            return false;
        }
        
        char ch1 = 0;
        char ch2 = 0;
        
        int len = firstStr.length();
        for (int i=0; i<len; i++) {
            ch1 = firstStr.charAt(i);
            ch2 = nextStr.charAt(i);
            if (ch1 == ch2) {
                continue;
                
            } else if (ch1 > ch2) {
                return true;
                
            } else if (ch1 < ch2) {
                return false;
            }
        }
        
        return false;
    }

[JAVA] File 배열을 파일명 순으로 정렬 : sortFileArray

[JAVA] File 배열을 파일명 순으로 정렬 : sortFileArray

File객체 배열(File[])을 파일명 순으로 정렬하는 메서드

———-

    private void sortFileArray(File[] fileArr) throws Exception {
        if (fileArr == null) {
            return;
        }
        
        if (fileArr.length < 2) {
            return;
        }
        
        File file1 = null;
        File file2 = null;
        File tempFile = null;
        
        int fileCount = fileArr.length;
        for (int i=0; i<fileCount; i++) {
            for (int k=i+1; k<fileCount; k++) {
                file1 = fileArr[i];
                file2 = fileArr[k];
                
                if (checkShouldChangeOrder(file1.getName(), file2.getName())) {
                    tempFile = fileArr[i];
                    fileArr[i] = file2;
                    fileArr[k] = tempFile;
                }
            }
        }
    }
    
    
    private boolean checkShouldChangeOrder(String firstStr, String nextStr) {
        if (firstStr == null) {
            firstStr = “”;
        }
        
        if (nextStr == null) {
            nextStr = “”;
        }
        
        if (firstStr.length() > nextStr.length()) {
            return true;
            
        } else if (firstStr.length() < nextStr.length()) {
            return false;
        }
        
        char ch1 = 0;
        char ch2 = 0;
        
        int len = firstStr.length();
        for (int i=0; i<len; i++) {
            ch1 = firstStr.charAt(i);
            ch2 = nextStr.charAt(i);
            if (ch1 == ch2) {
                continue;
                
            } else if (ch1 > ch2) {
                return true;
                
            } else if (ch1 < ch2) {
                return false;
            }
        }
        
        return false;
    }

[포토샵] 흑곰의 포토샵 기초강좌

[포토샵] 흑곰의 포토샵 기초강좌

직접 만든 포토샵 기초강좌. 원제는 ‘속성강좌’.

왕초보 30분만에 포토샵 기초 배우기 정도로 생각하면 된다.

2015년 1월 11일에 지인에게 주기 위해 작성했던 것.

서식은 신경쓰지 않고 대충 만들었으니 이해바람. PDF파일로 첨부했음.