[iOS] due to error: iCloud Keychain is disabled

[iOS] [AutoFill] Cannot show Automatic Strong Passwords for app bundleID: 패키지명 due to error: iCloud Keychain is disabled

[AutoFill] Cannot show Automatic Strong Passwords for app bundleID: com.company.project(패키지명) due to error: iCloud Keychain is disabled

iOS 개발시 Xcode콘솔에 위와 같은 메시지 출력되는 경우, iCloud 키체인을 켜준다.

아이폰에서 [설정] – [암호 및 계정] – 계정 항목의 [iCloud] – [키체인] 을 [끔]에서 [켬]으로 변경하면 더 이상 위 메시지 나오지 않는다.

이렇게 iCloud 키체인을 켠 이후에는 아래와 같은 메시지가 나올 수도 있다.

Automatic Strong Passwords for app bundleID: com.company.project(패키지명) due to error: Cannot save passwords for this app. Make sure you have set up Associated Domains for your app and AutoFill Passwords is enabled in Settings

여기서부터는 해결방법을 찾아보지 않았다. 아마도 일련의 메시지들은, 비밀번호 등을 키체인에 저장하려고 하는데 권한 관련 설정이 미비하여 나타나는 것으로 보인다.

[iOS] 신뢰할 수 없는 기업용 개발자

[iOS] 신뢰할 수 없는 기업용 개발자

신뢰할 수 없는 기업용 개발자. 이 iPhone이 ‘iPhone Distribution: Company Name(실제 회사이름)’을(를) 신뢰하지 않습니다. 개발자를 신뢰하기 전에는 해당 개발자의 기업용 앱을 사용할 수 없습니다.

위와 같은 토스트 메시지 뜨는 경우.

[설정] – [일반] – [기기 관리 (또는 프로파일 및 기기 관리] – [Company Name(실제 회사이름)] – [‘Company Name’을(를) 신뢰함]  – [신뢰] 클릭.

[Unity] Visual Studio 자동완성 (Ctrl + Space) 안되는 문제

[Unity] Visual Studio 자동완성 (Ctrl + Space) 안되는 문제

Unity에서 C# Script 파일(확장자 cs인 파일)을 더블클릭하여 Visual Studio 로 열었는데, Ctrl + Space 키를 눌러도 자동완성이 되지 않을 경우.

유니티 상단 메뉴의 [Edit] – [Preferences…]  – Preferences 창이 뜨면, 좌측의 [External Tools] 클릭 – External Script Editor 를 [Visual Studio 20xx] 로 지정하면 된다.

그림출처 : https://stackoverflow.com/questions/42597501/autocompletion-not-working-in-visual-studio 

[Android] compileSdkVersion, targetSdkVersion 다르게 지정하는 이유

[Android] compileSdkVersion, targetSdkVersion 다르게 지정하는 이유

안드로이드 프로젝트의 버전을 하나로 통일할 경우 문제가 발생하는 경우가 있었다.

필수적으로 넣어야 하는 권한처리 코드가 버전 23 (M, Marshmallow) 이상에서만 유효했다.

그런데 프로젝트에 포함된 라이브러리 중 하나가 버전 21 (L, Lollipop) 을 초과한 버전에서는 오류가 발생했다.

해당 라이브러리는 타 업체에서 개발한 것으로, 이것을 바꾸는 것은 현실적으로 무리였다.

그렇다고 SDK 버전을 21로 낮추자니, 권한처리 쪽 코드가 빨간색으로 표시되며 빌드 실패하였다.

결론은 compile SDK 버전과 target SDK 버전을 다르게 지정하여 해결하였다.

1. build.gradle 파일 수정
(1) compileSdkVersion 을 23 으로 지정 (권한처리 코드가 빌드 오류나지 않도록 지정)
(2) minSdkVersion 을 14 로 지정
(3) targetSdkVersion 을 21 로 지정 (특정 라이브러리를 동작시키기 위해 지정)

2. AndroidManifest.xml 파일 수정
(1) android:minSdkVersion 을 14 로 지정
(2) android:targetSdkVersion 을 21 로 지정 (특정 라이브러리를 동작시키기 위해 지정)

[TOMCAT] 존재하는 파일 404 에러 발생하는 경우 (tomcat webapp 위치)

[TOMCAT] 존재하는 파일 404 에러 발생하는 경우 (tomcat webapp 위치)

tomcat 에 넣은 프로젝트의 클래스 위치, 라이브러리 위치, webapp 위치를 재확인한다.

1. 클래스 위치

클래스는 WEB-INF 밑의 classes 폴더 안에 넣는다.

ex) /project_dir/tomcat/webapps/WEB-INF/classes

2. 라이브러리 위치

라이브러리(jar)는 WEB-INF 밑의 lib 폴더 안에 넣는다.

ex) /project_dir/tomcat/webapps/WEB-INF/lib

3. webapp 위치

webapp(jsp, js, css 등이 들어있는 폴더)은 tomcat 폴더 바깥에 두고, server.xml 의 Context docBase 의 값을 수정한다.

ex) /project_dir/webapp

자세히 설명하면 다음과 같다.

(1) webapp 폴더는 /project_dir/webapp 에 위치시킨다.

여기서 webapp 폴더란 jsp, js, css 등이 들어있는 폴더이다.

(2) server.xml 파일을 수정

server.xml 은 tomcat 폴더 하위 conf 폴더 안에 있다.

ex) /project_dir/tomcat/conf/server.xml

server.xml 파일 하단에서 Context docBase 를 찾아 수정한다.

<Context docBase=”/project_dir/webapp” path=”/” reloadable=”true”/></Host>

cf) <Context docBase=”실제 바라볼 폴더경로” path=”/매핑주소” reloadable=”true”/></Host>

[TOMCAT] 세션에 setAttribute 한 값이 null 인 경우 (ex : userId null)

[TOMCAT] 세션에 setAttribute 한 값이 null 인 경우 (ex : userId null)

세션에 setAttribute 한 값을 getAttribute 했을 때 null인 경우 발생.

예를 들어 로그인하고 사용자 아이디를 세션에 담았는데 userId 가 null 인 경우.

크게 두 가지 케이스가 있다.

1. setAttribute 한 이후 곧바로 getAttribute한 경우

setAttribute 하고, 페이지를 한 단계 건너가야 getAttribute 할 수 있음.

2. 같은 아이피(또는 같은 도메인)로 톰캣을 2개 띄운 경우

쿠키값을 덮어써서 올바른 세션을 찾지 못할 수 있다.

포트가 달라도 동일한 아이피를 사용하면 쿠키값을 덮어쓴다.

문제 해결은 톰캣 2개의 JSESSIONID 를 다르게 설정하면 된다.

아니면 톰캣 1은 아이피로 접속, 톰캣 2는 도메인으로 접속하면 쿠키값을 유지할 수 있다.

* 참고 : 세션은 쿠키에 기반한 기술이다. 세션 데이터는 서버에 있지만, 그것을 찾아가기 위한 세션 아이디는 브라우저 쿠키에 담아둔다.

TSTCON32.EXE 사용방법 (ActiveX 메서드 테스트)

TSTCON32.EXE 사용방법 (ActiveX 메서드 테스트)

TSTCON32.EXE 는 ActiveX Control Test Container(액티브엑스 컨트롤 테스트 컨테이너) 이다.

ActiveX (OCX 파일) 내의 메서드를 테스트해볼 수 있다.

한 마디로 API가 없거나 API 매뉴얼이 다소 미흡한 ActiveX를 활용하고자 할 때 큰 도움이 되는 프로그램이다.

1. TSTCON32.EXE 실행

TSTCON32.EXE 파일을 실행한다.

 

2. 테스트할 ActiveX 추가

상단메뉴의 [Edit] – [Insert New Control…] 을 클릭한다.

 

목록에서 테스트하고 싶은 컨트롤을 선택, [OK] 버튼을 클릭한다.

여기서는 대표적인 예로 HwpCtrl Control(아래아한글 프로그램 컨트롤)을 추가한다.

 

ActiveX 컨트롤이 추가되었다.

 

3. 컨트롤의 사용

한글 컨트롤을 예로 들면, 빈 문서가 열린 것과 같다. 문서 안에 글자를 써넣을 수도 있고, 표를 만들 수도 있다.

화면 하단에는 콘솔 로그가 보인다. 

예로 든 한글 컨트롤의 경우, 컨트롤 내에서 스크롤을 하면 OnScroll 이벤트를 호출하고,

컨트롤 내에서 마우스 좌클릭을 하면 OnMouseLButtonDown 이벤트를 호출하는 것을 볼 수 있다.

(모든 ActiveX 공통사항은 아니고, 한글 컨트롤만 해당사항임)


4. 메서드 테스트

상단 메뉴바의 보라색 상자 아이콘(Invoke Methods)를 클릭한다. 또는, 상단 메뉴 [Control] – [Invoke Methods…] 를 클릭한다.

Invoke Methods 창이 뜨면, Method Name 콤보박스에서 메서드를 선택하고, Parameters 리스트 박스의 항목들을 클릭하여 각 파라미터를 [Set Value] 버튼으로 지정할 수 있다. [Invoke] 버튼을 클릭하면 해당 메서드가 실행된다.

즉, TSTCON32 프로그램을 통해 (1) 특정 ActiveX가 소유한 메서드 목록을 볼 수 있으며, (2) 각 메서드에 필요한 파라미터를 볼 수 있다. 또한, (3) 해당 컨트롤에 각 메서드를 실제 사용하면 어떤 효과가 있는지를 직접 확인할 수 있다.

한 마디로 API가 없거나 API 매뉴얼이 다소 미흡한 ActiveX를 활용하고자 할 때 큰 도움이 되는 프로그램이다.

[Android] 크롬에서 안드로이드 디버깅 (안드로이드 원격 디버깅)

[Android] 크롬에서 안드로이드 디버깅 (안드로이드 원격 디버깅)

노트북에 안드로이드 기기 USB로 연결해놓은 상태에서

크롬 열고 주소창에 chrome://inspect 입력.

참고사이트 : https://developers.google.com/web/tools/chrome-devtools/remote-debugging/?hl=ko 

[한글API] HwpCtrl HyperlinkMode 의 사용 (HwpCtrl Event NotifyMessage 활용)

[한글API] HwpCtrl HyperlinkMode 의 사용 (HwpCtrl Event NotifyMessage 활용)

한글 OCX 컨트롤(ActiveX) 에서 열린, 한글 문서 내의 하이퍼링크들이 동작하지 않는 문제 발생.

기존에 사용하고 있던 도메인을 새로운 도메인으로 변경하여, 기존 주소가 404 NOT FOUND 오류 발생하는 문제였다.

하이퍼링크 이벤트를 자바스크립트 함수(js function)로 치환하였고, 자바스크립트 코드에서 URL을 간단히 replace하여 해결했다.

한글 컨트롤에서 하이퍼링크를 클릭하면 해당 URL을 윈도우의 기본 브라우저로 띄워주는데,

HyperlinkMode 가 0(기본값)일 경우 그러하다.

HyperlinkMode  를 1 로 변경하면 하이퍼링크 이벤트를 자바스크립트로 치환 가능하다.

1. 한글 컨트롤 하이퍼링크 모드 변경

한글 컨트롤 생성 이후 HyperlinkMode 를 1로 지정하면, 하이퍼링크 이벤트를 js function 으로 치환할 수 있다.
ex) 한글컨트롤변수명.HyperlinkMode(1);

2. 자바스크립트 함수 생성

js function 생성방법은 HTML에 다음과 같은 코드가 있으면 된다.
ex)
<SCRIPT LANGUAGE=”javascript” FOR=”한글컨트롤변수명” EVENT=”NotifyMessage(Msg, WParam, LParam)”>
    // 하이퍼링크 클릭시 실행되는 함수 정의.
    // “한글컨트롤변수명.HyperlinkMode(1);” 명령어 사용하면, 하이퍼링크 이벤트 대신 이 함수가 실행된다.

    // Msg 가 HyperLinkClicked 이면 하이퍼링크.
    // WParam 가 1이면 외부 하이퍼링크, 0이면 문서 내 이동이다.
    if (Msg == “HyperLinkClicked” && WParam == “1”) {
        var setObj = HwpCtrl.CtrlObj.GetMessageSet();
        var linkUrl = setObj.Item(“String”);

        alert(“linkUrl : ” + linkUrl);
        window.open(linkUrl);
    }
</SCRIPT>

cf. HwpCtrl Event 의 활용

한글API에 Event라고 되어 있는 사항들이, 항목 2번과 같이 기존 이벤트를 js function 으로 치환할 수 있다.

그 목록은 다음과 같다.

1. NotifyMessage

* NotifyMessage(string Msg, number WParam, number LParam)

Msg 값이 FieldClickHereClicked 인 경우에는 필드 컨트롤 클릭, HyperLinkClicked 인 경우에는 하이퍼링크 클릭이다.

2. OnMouseLButtonDown

* OnMouseLButtonDown(number x, number y)

3. OnScroll

* OnScroll (number WParam, number LParam)

참고사이트

한/글 2010 컨트롤 API 가이드

https://www.hancom.com/board/devdataView.do?board_seq=47&artcl_seq=4226&pageInfo.page=1&artcl_clss=&search_text=

[iOS] NSString 문자열 결합

[iOS] NSString 문자열 결합 

NSString *str1 = @”aaa”; 

NSString *str2 = @”bbb”;

NSString *str3 = [str1 stringByAppendingString:str2];

NSLog(@”%@”, str3);

결과는 aaabbb 가 된다.

출처 : https://ngee.tistory.com/226

[iOS] try catch finally

[iOS] try catch finally

@try {
    // aaaa 메소드를 실행
    [self aaaa];

} @catch (NSException *e) {
    // 예외 발생
    NSLog(@”exceptionName %@, reason %@”, [e name], [e reason]);

} @finally {
    NSLog(@”This always happens.”);
}

출처 : https://fra3il.tistory.com/104

[iOS] The xcode build system has crashed

[iOS] The xcode build system has crashed



1. XCode 상단메뉴의 [File] – [Project Settings…] 클릭

2. Build System 의 항목을 Legacy Build System 으로 선택

그림 및 내용 출처 : https://stackoverflow.com/questions/50870787/xcode-9-4-unexpected-service-error-the-xcode-build-system-has-crashed

[iOS] Provisioning profile doesn’t include the currently selected device iphone

[iOS] Provisioning profile doesn’t include the currently selected device iphone

1. General 탭을 열기

2. General 탭에서 Automatically manage signing 체크

그림 및 내용 출처 (1) : https://stackoverflow.com/questions/19407439/general-tab-missing

그림 및 내용 출처 (2) : https://dreamaz.tistory.com/347

[JAVA] RuntimeException

[JAVA] RuntimeException

그간 RuntimeException 에 대해 오해를 하고 있었는데, catch 를 무시하고 상위로 전달된다고 생각하고 있었다.

RuntimeException 은 [throws 익셉션명] 절을 붙이지 않은 메서드에서도 throw 가능하지만(상위로 전달되지만) catch 를 무시하는 것은 아니다. try ~ catch 로 감싸면 잘 잡힌다.

간단히 테스트해보면 알 수 있는데 왜 여태 몰랐을까.

[JAVA] HttpClient, HttpPost 예제

[JAVA] HttpClient, HttpPost 예제

String url = “http://naver.com/“;
HttpClient client = HttpClientBuilder.create().build();
HttpPost request = new HttpPost(url);

List<NameValuePair> params = new ArrayList<NameValuePair>();
params.add(new BasicNameValuePair(“param1”, “파라미터1”));
params.add(new BasicNameValuePair(“param2”, “파라미터2”));

// 파라미터에 urlEncoding 적용
request.setEntity(new UrlEncodedFormEntity(params));
HttpResponse response = client.execute(request);

이클립스 단축키 : Ctrl + T

이클립스 단축키 : Ctrl + T

단축키 F3 은 임플리먼트된 클래스로 들어갈 수 없음. Ctrl + T를 쓸것
(Ctrl + 클릭보다 속도가 빠름)

IE ActiveX 레지트스리 값 변경

IE ActiveX 레지트스리 값 변경


<Zones>
Zones 키에는 컴퓨터에 대해 정의된 각 보안 영역을 나타내는 키가 포함되어 있습니다. 기본적으로 다음과 같은 다섯 개의 영역(0-4)이 정의됩니다.
값        설정
——————————
0        내 컴퓨터
1        로컬 인트라넷 영역
2        신뢰할 수 있는 사이트 영역
3        인터넷 영역
4        제한된 사이트 영역

<DWORD>
각 DWORD 값은 0, 1 또는 3과 같습니다. 일반적으로 설정 0은 특정 작업을 허용하도록 설정하고, 설정 1은 프롬프트가 나타나도록 하며 설정 3은 특정 작업을 허용하지 않습니다.

값        설정
——————————

1001 ActiveX 컨트롤 및 플러그 인: 서명된 ActiveX 컨트롤 다운로드
1004 ActiveX 컨트롤 및 플러그 인: 서명 안 된 ActiveX 컨트롤 다운로드
1200 ActiveX 컨트롤 및 플러그 인: ActiveX 컨트롤 및 플러그 인 실행
1201 ActiveX 컨트롤 및 플러그 인: 스크립팅하기 안전하지 않는 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트
1206 기타: Internet Explorer 웹 브라우저 컨트롤 스크립팅 허용 ^
1207 예약됨 #
1208 ActiveX 컨트롤 및 플러그 인: 이전에 사용되지 않은 ActiveX 컨트롤을 묻지 않고 실행하도록 허용 ^
1209 ActiveX 컨트롤 및 플러그 인: 스크립틀릿 허용
120A ActiveX 컨트롤 및 플러그 인: ActiveX 컨트롤 및 플러그 인: 사이트별 재정의(도메인 기반) ActiveX 제한
120B ActiveX 컨트롤 및 플러그 인: 사이트별 재정의(도메인 기반) ActiveX 제한
1400 스크립팅: Active 스크립팅
1402 스크립팅: Java 애플릿 스크립팅
1405 ActiveX 컨트롤 및 플러그 인: 스크립팅하기 안전한 것으로 표시된 ActiveX 컨트롤 스크립트
1406 기타: 도메인 간의 데이터 소스 액세스
1407 스크립팅: 프로그램 클립보드 액세스 허용
1408 예약됨 #
1601 기타: 암호화되지 않은 폼 데이터 전송
1604 다운로드: 글꼴 다운로드
1605 Java 실행 #
1606 기타: 사용자 데이터 보존 ^
1607 기타: 다른 도메인 간의 하위 프레임 탐색
1608 기타: META REFRESH 허용 * ^
1609 기타: 혼합된 콘텐츠 표시 *
160A 기타: 파일을 서버에 업로드할 때 로컬 디렉터리 경로 포함 ^
1800 기타: 바탕 화면 항목 설치
1802 기타: 끌어서 놓기 또는 파일 복사 및 붙여넣기
1803 다운로드: 파일 다운로드 ^
1804 기타: IFRAME에서 프로그램 및 파일 실행
1805 웹 보기에서 프로그램 및 파일 실행 #
1806 기타: 응용 프로그램 및 안전하지 않은 파일 실행
1807 예약됨 ** #
1808 예약됨 ** #
1809 기타: 팝업 차단 사용 ** ^
180A 예약됨 #
180B 예약됨 #
180C 예약됨 #
180D 예약됨 #
1A00 사용자 인증: 로그온
1A02 컴퓨터에 저장된 영구 쿠키 허용 #
1A03 세션 단위 쿠키 허용(저장 안 함) #
1A04 기타: 인증서가 없거나 하나만 있는 경우 클라이언트 인증서 선택 안 함 * ^
1A05 제 3 사 영구 쿠키 허용 *
1A06 제 3 사 세션 쿠키 허용 *
1A10 개인 정보 설정 *
1C00 Java 사용 권한 #
1E05 기타: 소프트웨어 채널 사용 권한
1F00 예약됨 ** #
2000 ActiveX 컨트롤 및 플러그 인: 바이너리 및 스크립트 동작
2001 .NET Framework 기반 구성 요소: Authenticode로 서명된 구성 요소 실행
2004 .NET Framework 기반 구성 요소: Authenticode로 서명되지 않은 구성 요소 실행
2100 기타: 파일 확장명이 아닌 파일 내용으로 파일 열기 ** ^
2101 기타: 낮은 권한을 가진 웹 콘텐츠 영역에 있는 웹 사이트에서 이 영역으로 이동할 수 있습니다. **
2102 기타: 크기 또는 위치 제한을 가지지 않은 스크립트에서 시작한 창을 허용합니다. ** ^
2103 스크립팅: 스크립트를 통해 상태 표시줄 업데이트 허용 ^
2104 기타: 웹 사이트에서 주소 또는 상태 표시줄 없이 창을 열도록 허용 ^
2105 스크립팅: 웹 사이트에서 스크립팅된 창을 사용하여 정보를 요청하도록 허용 ^
2200 다운로드: 파일 다운로드에 대해 자동 확인 ** ^
2201 ActiveX 컨트롤 및 플러그 인: ActiveX 컨트롤에 대해 자동으로  확인 ** ^
2300 기타: 웹 페이지에서 액티브 콘텐츠에 대해 제한된 프로토콜을 사용하는 것을 허용 **
2301 기타: 피싱 필터 사용 ^
2400 .NET Framework: XAML 브라우저 응용 프로그램
2401 .NET Framework: XPS 문서
2402 .NET Framework: 느슨한 XAML
2500 보호 모드 사용[Vista 전용 설정] #
2600 .NET Framework 설치 사용 ^

*  Internet Explorer 6 이상 설정을 나타냄
** Windows XP 서비스 팩 2 이상 설정을 나타냄
#  Internet Explorer 7의 사용자 인터페이스에 표시되지 않는 설정을 나타냄
^  사용 또는 사용 안 함의 두 옵션만 있는 설정을 나타냄

<신뢰할 수 있는 사이트(Zones\2) 에 대한 DWORD 값 변경의 예>

// Scripting: Enable XSS Filter(1409)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1409” 값을 “3”으로 변경

// 도메인 간의 데이터 소스 액세스(1406)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1406” 값을 “0”으로 변경

// 서로 다른 도메인 간의 하위프레임 이동(1607)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1607” 값을 “0”으로 변경

// 안전하지 않은 것으로 표시된 ActiveX 컨트롤 초기화 및 스크립트(1201)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1201” 값을 “0”으로 변경

// ActiveX 컨트롤 및 플러그 인: 스크립틀릿 허용(1209)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1209” 값을 “0”으로 변경

// 크기 또는 위치 제한을 가지지 않은 스크립트에서 시작한 창을 허용합니다.(2102)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “2102” 값을 “0”으로 변경

// 액티브 스크립팅 허용(1400)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1400” 값을 “0”으로 변경

// ActiveX 컨트롤과 플러그인 실행(1200)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1200” 값을 “0”으로 변경

// 암호화되지 않은 폼 데이터 제출(1601)
“HKEY_CURRENT_USER” – “Software\Microsoft\Windows\CurrentVersion\Internet Settings\Zones\2” – “1601” 값을 “0”으로 변경

// XMLHTTP
“HKEY_CURRENT_USER” – “Software\\Microsoft\\Internet Explorer\\Main” – “XMLHTTP” 값을 “1”로 변경

// 임시 인터넷 파일 설정을 웹 페이지를 열때마다로 설정(SyncMode5)
“HKEY_CURRENT_USER” – “Software\\Microsoft\\Windows\\CurrentVersion\\Internet Settings” – “SyncMode5” 값을 “3”으로 변경

출처 : 고급 사용자를 위한 Internet Explorer 보안 영역 레지스트리 항목

https://support.microsoft.com/ko-kr/help/182569/internet-explorer-security-zones-registry-entries-for-advanced-users 

서블릿은 싱글턴이다

서블릿은 싱글턴이다

서블릿은 싱글턴이다.
싱글턴 패턴을 사용하지 않았지만 싱글턴이다.
WAS가 서블릿 인스턴스를 단 1개만 생성한다.

인스턴스는 1개 이지만 스레드는 사용자 요청마다 생성된다.
그러므로 여러 개의 메서드가 동시에 호출될 수 있다.

즉, 객체의 멤버변수 (final이 아닌 경우) 사용에 주의해야 한다.
여러 메서드에서 같이 사용하는 멤버변수 값을 수정하면 문제가 생길 수 있다.

[Tip] 일러스트레이터 없이 ai 확장자 열기

[Tip] 일러스트레이터 없이 ai 확장자 열기

ai 파일 여는 법.

확장자를 pdf 로 수정하고, pdf 뷰어로 열면 된다.

[Tomcat] mod_jk.so 다운로드

[Tomcat] mod_jk.so 다운로드

아파치(Apache)와 톰캣(Tomcat)을 연동하기 위한 라이브러리.

* 윈도우 서버, Tomcat 7.0 에서 사용해본 결과 잘 작동함.

* 실행파일이라 그런지 첨부해도 타인에게는 첨부파일이 보이지 않네요…

[Linux] 리눅스 ip 확인

[Linux] 리눅스 ip 확인

ifconfig -a | grep “inet “ | grep “Bcast:” | awk ‘{print $2}’ | awk -F: ‘{print $2}’ 입력

또는

ip route 입력

​출처 : https://zetawiki.com/wiki/%EB%A6%AC%EB%88%85%EC%8A%A4_%EA%B3%B5%EC%9D%B8_IP_%ED%99%95%EC%9D%B8

 

[Android] jks 파일 분실 해서는 안되는 이유

[Android] jks 파일 분실 해서는 안되는 이유

확장자 jks 파일은 인증서 파일이다. 안드로이드 앱을 묶을 때 jks 파일을 사용한다.

전임 모바일 담당자로부터 jks 파일을 분실하면 그냥 새로 만들면 된다고 인수인계 받았다.

새로 만들어서 앱을 묶나, 기존에 있는 파일을 사용해서 앱을 묶나 차이가 없다는 것이다.

나중에 알고 보니 전혀 그렇지 않았다.

기존 앱을 묶을 때 사용했던 jks 파일을 사용해야, 새로 묶은 앱이 이상 없이 잘 설치된다.

일종의 업데이트 행위가 이루어진다.

만약 기존 앱과 다른 jks 파일을 사용해서 앱을 묶는다면, 분명 동일한 앱임에도 불구하고 패키지가 충돌난다. (패키지가 이름이 같은 기존 패키지와 충돌합니다)

새 버전의 앱을 깔기 위해서 이전 앱을 삭제해야만 하는 것이다.

내용이 여기서 끝나면 섭하니, 검색을 통해 들어오신 분들에게 도움이 될만한 링크를 남겨둔다.

[문제해결] 

1. jks 파일 분실했을 경우

1-1. Google Play 지원팀에 문의

마켓에 배포한 앱의 경우, 미리 Google Play App Signing 을 설정해두었다면 복구할 수 있다.

Keystore파일(PEM 파일)을 새로 생성하고 Google Play 지원팀에 문의를 작성하면 된다고 한다.

관련 링크 : https://zeallat.wordpress.com/2018/02/21/android-keystore-%EB%B6%84%EC%8B%A4%EC%8B%9C-%ED%95%B4%EA%B2%B0-%EC%82%AC%EB%A1%80/comment-page-1/

1-2. apk 파일로부터 jks 파일을 복구 (불가능)
apk 파일로부터 jks 파일을 복구하는 방법을 열심히 찾아보았지만… 아직까지 확인된 바로는 없다.
(아시는 분 댓글 달아주시길)
지금 막 떠오른 방법으로는 기존 apk 파일의 압축을 풀어서, 소스에 관련된 파일들만 새로운 apk 파일 내에서 가져다가 엎어치고, apk를 다시 묶으면 어떨까 싶은데 될지 잘 모르겠다.
2, jks 파일의 비밀번호를 분실했을 경우
2-1. 로그를 통해 jks 패스워드 찾기
안드로이드는 빌드를 할 때마다 로그를 남겨놓는다. AndroidStudio 폴더 내의 idea.log, idea.log.1, idea.log.2 … 등 파일을 열어본 이후 password, alias, key, store 등의 키워드를 검색해보면 찾을 수 있다고 한다.
2-2. Bruteforce(모든 값 대입)를 통해 jks 패스워드 찾기
정말 엄청난 프로그램인데… 가용한 모든 경우의 수를 대입해서 패스워드를 찾아내는 프로그램인 <Android-keystore-password-recover>가 있다.

[SVN] ask the administrator to create a pre-revprop-change hook 오류 해결

[SVN] ask the administrator to create a pre-revprop-change hook 오류 해결

이클립스에서 SVN Comment 수정하는 방법은 다음과 같다.

(1) 프로젝트 폴더 위에서 마우스 우클릭 – [Team] – [Show History] 로 히스토리 탭을 연다.
(2) 히스토리 탭에서 Comment가 비어있는 Revision 라인을 선택하고 마우스 우클릭 – [Set Commit Properties] 를 선택한다.
(3) 원하는 Comment를 입력하고 [OK] 버튼을 클릭한다.

이 때 아래와 같은 에러가 발생하는 경우가 있다.

Disabled repository feature
svn: Repository has not been enabled to accept revision propchanges;
ask the administrator to create a pre-revprop-change hook

작성자가 로그 메시지를 수정할 수 없게 되어 있다는 메시지이다. 해결방법은 다음과 같다.

(1) svn 폴더로 이동한다.

cd [리파지토리 경로]

ex) cd /home/svn/project_name

ls 해보면 아래와 같은 파일들이 있다.

conf  dav  db  format  hooks  locks  README.txt

(2) hooks 폴더 안으로 이동

cd hooks

ls 해보면 아래와 같은 파일들이 있다.

post-commit.tmpl  post-revprop-change.tmpl  pre-commit.tmpl  pre-revprop-change.tmpl  start-commit.tmpl
post-lock.tmpl    post-unlock.tmpl          pre-lock.tmpl    pre-unlock.tmpl

(3) pre-revprop-change.tmpl 을 복사하여 pre-revprop-change 파일을 만든다.

cp pre-revprop-change.tmpl pre-revprop-change

(4) vi 로 파일을 열어서 수정한다. (윈도우라면 메모장으로 파일을 수정)

if [ “$ACTION” = “M” -a “$PROPNAME” = “svn:log” ]; then exit 0; fi 라인 아래에,

if [ “$ACTION” = “M” -a “$PROPNAME” = “svn:author” ]; then exit 0; fi 내용을 추가한다.

만약 위와 같이 파일을 수정하지 않거나 잘못 수정했을 경우 exit code 1 오류가 발생한다.

(5) 실행 권한을 부여한다. (윈도우라면 bat 확장자를 붙여서 파일명 수정)

chmod +x pre-revprop-change

만약 위와 같이 실행 권한을 부여하지 않으면, exit code 255 오류가 발생한다.

A repository hook failed
svn: Revprop change blocked by pre-revprop-change hook (exit code 255) with no output.

참고사이트 1) https://junho85.pe.kr/48

 

[Android Studio] error: illegal character: ‘\ufeff’

[Android Studio] error: illegal character: ‘\ufeff’

안드로이드 스튜디오에서 빌드시 아래 오류가 발생하는 경우

C:\FilePath\FileName.java:1: error: illegal character: ‘\ufeff’
C:\FilePath\FileName.java:1: error: class, interface, or enum expected

 

안드로이드 스튜디오 프로젝트 트리에서 오류가 발생한 특정 파일 위에서 마우스 우클릭, “Remove BOM” 을 클릭하여 해결한다.

 

만약 위의 방법으로 적용하기 어렵다면 Notepad++ 로 오류가 나는 해당 파일을 열고, 상단 메뉴의 [인코딩] – [UTF-8 (BOM 없음)로 표시] 를 선택하고 저장한다.

 

참고로 BOM(Byte Order Mark)은 유니코드 파일 첫 부분에 눈에 보이지 않게 추가하는 문자열이다.

유니코드의 인코딩 방식을 표시하기 위한 문자열이다.
텍스트 에디터로 열었을 때는 보이지 않고, 헥스 에디터로 열었을 때만 볼 수 있다.

BOM의 종류는 아래와 같다.

UTF-8 : EF BB BF
UTF-16 Big Endian : FE FF
UTF-16 Little Endian : FF FE
UTF-32 Big Endian : 00 00 FE FF
UTF-32 Little Endian : FF FE 00 00

참고사이트) https://brownbears.tistory.com/124

Oracle SQL Developer 에서 MySQL/MariaDB 사용하기

Oracle SQL Developer 에서 MySQL/MariaDB 사용하기


Oracle SQL Developer 에서 MySQL 또는 MariaDB 에 접근하여 사용하는 방법이다.

1. Oracle SQL Developer 에서 MySQL/MariaDB 연결
1-1. Oracle SQL Developer 기동

1-2. SQL Developer 상단 메뉴의 [도구] 선택 – [환경설정] 항목을 클릭

  

1-3. [환경설정] 좌측 메뉴의 [데이터베이스] – [타사 JDBC 드라이버] 클릭 – 하단의 [항목 추가] 버튼 클릭

1-4. 특정위치의 mysql-connector-java-5.1.7-bin.jar 클릭 후 [선택] 버튼 클릭
ex) C:\test\mysql-connector-java-5.1.6-bin.jar

* 본 포스트에 첨부되어 있는 mysql-connector-java-5.1.6-bin.jar 파일을 다운로드 받아 적당한 폴더에 위치시킨다.


 

1-5. jar 경로를 확인하고 [확인] 클릭

 

 1-6. SQL Developer 좌측의 초록색 화살표 버튼 클릭

(1) 접속 이름, 사용자 이름, 비밀번호를 입력한다.
(2) 새로 생성된 MySQL 탭을 선택한다.
(3) 호스트 이름, 포트를 입력한다.
(4) [테스트] 버튼을 눌러 [상태: 성공]이 나오면 잘 연결된 것이다.

참고) MySQL/MariaDB는 접속시 기본적으로 autocommit 모드로 동작하기 때문에 insert나 update, delete 명령시 주의해야 함.

2. MySQL/MariaDB 기본 명령어
2-1. show databases;
데이터베이스 목록을 조회한다.

2-2. use [데이터베이스 이름];
특정 데이터베이스를 사용한다. 이 명령어를 수행해야 테이블을 조회할 수 있다.

2-3. show tables;

테이블 목록을 조회한다.

2-4. select * from [테이블 이름];
특정 테이블의 내용을 조회한다.


[Android] 안드로이드 apk 디컴파일

[Android] 안드로이드 apk 디컴파일

안드로이드 apk 확장자 파일을 디컴파일하는 방법이다.

1. jd-gui 다운로드 및 압축 해제

우선 jd-gui 프로그램을 준비해야 한다.

jd-gui는 자바로 만들어진 jar 파일을 디컴파일해서 볼 수 있는 프로그램이다.

다운로드 받아서 적당한 위치에 압축 해제 해두면 된다.

본 포스트에 첨부된 jd-gui-windows-1.6.3.zip 파일을 다운로드 받자.

또는 https://github.com/java-decompiler/jd-gui/releases 주소에 접속해서 가장 최신 버전의 jd-gui-windows-[버전].zip 파일을 다운로드 받자.

2. dex2jar 다운로드 및 압축 해제

2-1. dex2jar-2.0.zip 파일 다운로드

첨부된 dex2jar-2.0.zip 파일을 다운로드한다. 또는 아래 사이트에서 다운로드한다.

cf) https://sourceforge.net/projects/dex2jar/

2-2. 특정위치에 압축 해제

기억하기 쉬운 위치에 압축 해제한다. 필자는 C:\dex2jar-2.0 위치에 압축해제하였다.

3. apk 파일을 디컴파일한 jar파일 얻기 (apk 파일 -> jar 파일)

3-1. apk 파일을 준비한다. (ex : android_test.apk)

3-2. apk 파일의 확장자를 zip 으로 변경한다. (ex : android_test.zip)

3-3. 압축을 해제한다. classes.dex 파일이 들어있는 것을 확인할 수 있다.


 

3-4. cmd 에서 cd 명령어로 classes.dex 파일이 존재하는 위치로 이동한다.

(ex : cd C:\test\android_test)

[특정위치]\d2j-dex2jar.bat classes.dex 명령어를 실행한다.

(ex: C:\dex2jar-2.0\d2j-dex2jar.bat classes.dex)

3-5. classes-dex2jar.jar 파일이 생성된 것을 볼 수 있다. 이제 이 jar 파일을 jd-gui로 열어보면 된다.

(만약 apk 파일을 ProGuard 로 난독화한 경우, jar 파일을 열었을 때 난독화된 코드가 보일 것이다.)

4. ProGuard 난독화 해제한 jar 파일 얻기 (jar 파일 -> 난독화 해제한 jar 파일)

ProGuard 로 난독화한 apk 파일의 경우, 해당 apk 파일을 컴파일 할 때 사용했던 proguard.cfg 파일이 있으면 난독화 해제 가능하다.

쉽게 말해 아예 남이 만든 apk 파일이라면 proguard.cfg 파일이 없으므로 난독화 해제가 불가능하다.

본인 프로젝트 또는 회사 프로젝트라면 컴퓨터를 잘 뒤져서 proguard.cfg 파일을 찾아내도록 하자.

4-1. 첨부된 mapping.jar 다운로드

* 본 프로그램의 개발자 분은 Michael Brunner(GraxCode) https://github.com/GraxCode/AntiSmokeObfuscator 이며, 실행 가능한 jar 파일은 블로그 주소 https://ddmanager.tistory.com/25 에서 가져왔다. 이 자리를 빌어 감사드린다.

4-2. mapping.jar 파일을 특정 위치로 이동

나중에 난독화 해제된 jar파일이 떨어지게 되므로 기억하기 쉬운 위치로 지정하자.

필자는 C:\test\mapping 에 mapping.jar 파일이 위치하도록 했다.

4-3. mapping.jar 파일 실행

(1) cmd 실행

(2) mapping.jar 파일이 위치한 폴더로 이동 (cd C:\test\mapping)

(3) 자바로 mapping.jar 실행 (ex : java -jar mapping.jar)

4-4. ProGuard 난독화 해제

(1) [Load Jar] 버튼을 클릭하여 classes-dex2jar.jar 파일 위치를 지정한다. (ex : C:\test\android_test\classes-dex2jar.jar)

(2) [Load Mappings] 버튼을 클릭하여 proguard.cfg 파일 위치를 지정한다. (ex : C:\test\proguard.cfg)

(3) 콤보박스는 Proguard 를 선택

(4) [Reverse] 버튼 클릭

[Saved modified file!] 이라는 문구가 나오면 cmd가 가르키는 위치 (ex : C:\test\mapping) 로 가보자.

4-5. 난독화 해제된 jar 파일 얻기

classes-dex2jar.jar.correlated.jar 파일이 생성되었다. 난독화 해제된 jar 파일이며 jd-gui 에서 열어보면 된다.

참고사이트) https://ddmanager.tistory.com/25

.svn 폴더 wc.db 파일로 저장소 위치 확인 (SVN 주소 확인)

.svn 폴더 wc.db 파일로 저장소 위치 확인 (SVN 주소 확인)

형상관리도구 SVN 에 연결되었던 프로젝트 안에는 .svn 폴더가 위치한다.

.svn 폴더는 존재하는데 저장소 위치를 확인할 수 없을 때, 다음과 같이 찾을 수 있다.

1. .svn 폴더 및 wc.db 파일 경로 확인

먼저 .svn 폴더의 위치를 기억해두자.

본 포스트에서는 C:\example_project\.svn 이라고 가정한다.

 

.svn 폴더 안에는 entries, format, wc.db 등의 파일이 있다.

2. SQLite 다운로드

2-1. SQLite 사이트 접속 및 Download 메뉴 접근

 

SQLite 사이트 https://sqlite.org/ 에 접속하여 Download 메뉴를 클릭한다.

또는 곧바로 https://sqlite.org/download.html 페이지에 접속한다.

2-2. 윈도우용 SQLite 다운로드

 

Download 페이지에서 [Precompiled Binaries for Windows] 항목을 찾고, sqlite-tools-win32-x86-32900000.zip 파일을 다운받는다.

sqlite-dll 이 아닌 sqlite-tools 를 다운받는 것이 포인트다.

2-3. SQLite 압축 해제

 

다운로드 받은 SQLite zip 파일을 압축 해제한다.

이 때 폴더명이 sqlite-tools-win32-x86-3290000 등 복잡한 이름으로 해제될텐데, 편의상 기억하기 쉬운 폴더명으로 바꿔둔다.

필자는 폴더명을 sqlite로 지정했으며, sqlite3.exe 파일이 C:\sqlite\sqlite3.exe 에 위치하도록 이동시켰다.

3. SQLite 로 SVN 저장소 위치 select

3-1. SQLite 로 wc.db 열기


 

우선 cmd를 실행한다.

.svn 폴더로 이동한다. (명령어 cd C:\example_project\.svn)

SQLite 로 wc.db 를 열어본다. (명령어 C:\sqlite\sqlite3 wc.db)

[sqlite>] 라고 표시되면 잘 열린 것이다.

3-2. SQLite 에서 저장소 위치(SQL 주소) SELECT

명령어 .tables 를 입력하면 모든 테이블이 출력된다.

명령어 .header on 을 입력하면 SELECT 명령시 헤더가 표시된다.

명령어 .mode column 을 입력하면 SELECT 명령시 내용이 컬럼별로 표시된다.

select * from REPOSITORY; 명령어를 입력한다.

root 컬럼에 저장소 위치(SVN 주소)가 표시된다. (svn://~)

참고사이트 : http://egloos.zum.com/mcchae/v/10966689

[Android] Toast.makeText 에러 (AppName 앱을 중지하였습니다)

[Android] Toast.makeText 에러 (AppName 앱을 중지하였습니다)

안드로이드에서 토스트 메시지를 띄우는 코드는 다음과 같다.

public void doToastMsg(String str) {

    Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();
}

그런데 이 코드에서 “AppName 앱을 중지하였습니다” 메시지가 표시되며 앱이 죽는 경우가 있다.

E/AndroidRuntime: FATAL EXCEPTION: Thread-4
    Process: secuwiz.net.serviceapitest, PID: 12497
    java.lang.RuntimeException: Can’t create handler inside thread that has not called Looper.prepare()
        at android.os.Handler.<init>(Handler.java:200)
        at android.os.Handler.<init>(Handler.java:114)
        at android.widget.Toast$TN.<init>(Toast.java:622)
        at android.widget.Toast.<init>(Toast.java:137)
        at android.widget.Toast.makeText(Toast.java:385)
        at secuwiz.net.serviceapitest.OtpActivity.doToastMsg(OtpActivity.java:284)
        at secuwiz.net.serviceapitest.OtpActivity$StartRunnable.run(OtpActivity.java:212)
        at java.lang.Thread.run(Thread.java:762)

위와 같이 Can’t create handler inside thread that has not called Looper.prepare() 에러가 출력될 경우, 아래처럼 코드를 고쳐준다.

public void doToastMsg(String str) {
    if (Looper.myLooper() == null) {
         Looper.prepare();
     }

     Toast.makeText(getApplicationContext(), str, Toast.LENGTH_LONG).show();
     Looper.loop();
}

루퍼는 이벤트 루프와 메시지 큐의 레퍼런스를 가지고 있는 클래스이다. 

쓰레드가 메시지 큐에 접근할 수 있는 수단을 제공한다.

루퍼는 한 쓰레드에 단 1개만 존재할 수 있다. (한 번 더 생성하려고 시도할 경우 java.lang.RuntimeException: Only one Looper may be created per thread 오류가 발생)

Looper.prepare(); 코드로 루퍼를 생성하고,

Looper.loop(); 코드로 메시지 큐로부터 메시지를 디스패치 시킨다.

참고사이트) http://horajjan.blog.me/220963043229

https://m.blog.naver.com/kkk4687/220441125902

Eclipse 에 Vue.js 플러그인 설치 방법

Eclipse 에 Vue.js 플러그인 설치 방법

백엔드는 Spring 으로 되어 있고 프론트엔드는 Vue.js 로 되어 있을 때,

Visual Studio Code 를 사용하지 않고  Eclipse 하나로 백엔드/프론트엔드를 개발하는 방법.

1. 사전준비

본 포스트는 아래 작업이 모두 완료되었음을 전제로 한다.
(1) Eclipse 다운로드 및 설치
(2) JDK 다운로드 및 설치
(3) Node.js 다운로드 및 설치
(4) npm 사용을 위한 Node.js 경로의 환경변수 설정
    cf) cmd 에서 npm 명령어 실행했을 때 아래 그림과 같이 인식되면 정상

(5) Vue-cli 설치 (cmd 에서 npm install을 이용하여 vue-cli 설치)

 

2. vue.js 플러그인 설치


 

이클립스 상단 메뉴의 [Help] – [Eclipse Marketplace] 선택

 

vue 로 검색, Vue.js :: CodeMix 를 찾아서 [Install] 버튼 클릭하여 설치


 

Vue 프로젝트로 생성된 폴더 위에서 마우스 우클릭 – [Show In] – [Terminal] 클릭

 

하단에 만들어진 Terminal 콘솔에 명령어 입력
ex1) npm install => 노드 패키지 설치
ex2) npm run build => 빌드 수행 (ex : build.min.js 를 생성)
ex3) npm run dev => 테스트 서버를 띄움

3. Vue 파일 글자색 하이라이트 적용 방법 


 

위와 같이 확장자 vue인 파일의 글자색이 하이라이트 되지 않는 경우

 

이클립스 상단 메뉴의 [Window] – [Preferences] 클릭

 

Preferences 창에서 좌측 메뉴의 [General] – [Context Types] 선택
우측 내용의 [Text] – [HTML] 선택 – [add…] 버튼 클릭

 

[*.vue] 입력 후 [OK] 버튼 클릭

 

*.vue 항목을 클릭하고 하단의 Default encoding 값 [UTF-8]로 입력하고
우측의 [Update] 버튼 클릭
마지막으로 [Apply and Close] 버튼 클릭

 

하이라이트가 잘 적용되었음을 볼 수 있음.

참고사이트 : https://blog.naver.com/anytimedebug/221314355297

[JAVA] 자바 파일 선택 다이얼로그 (JFileChooser)

[JAVA] 자바 파일 선택 다이얼로그 (JFileChooser)

// 파일 선택 다이얼로그 정의
JFileChooser fileDialog = new JFileChooser();

// 폴더만 선택
fileDialog.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY);

// 파일만 선택
// fileDialog.setFileSelectionMode(JFileChooser.FILES_ONLY);

// 기본 폴더 위치 변경
File driveC = new File(“C:\\”);
if (driveC.exists()) {
    fileDialog.setCurrentDirectory(driveC);
}

// 파일 선택 다이얼로그 열기
int returnVal = fileDialog.showOpenDialog(null);
if (returnVal == 0) {
    // 파일 선택
    System.out.println(“Selected File Path : ” + fileDialog.getSelectedFile().getAbsolutePath());
}

자바 파일 읽기 자바 파일 쓰기 (2019.07.19)

자바 파일 읽기 자바 파일 쓰기 (2019.07.19)

자바파일읽기파일쓰기

파일읽기 파일쓰기

public static ArrayList<String> readFile(File file) throws IOException, Exception {
        if (file == null || !file.exists()) {
            return null;
        }

        ArrayList<String> resultList = null;

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

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

            String oneLine = null;
            while ((oneLine = bufferedReader.readLine()) != null) {
                if (resultList == null) {
                    resultList = new ArrayList<String>();
                }

                resultList.add(oneLine);
            }

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            close(bufferedReader);
            close(inputStreamReader);
            close(fileInputStream);
        }

        return resultList;
    }
    
    
    public static boolean writeFile(String filePath, ArrayList<String> stringList, boolean bAppend) throws IOException, Exception {
        if (filePath == null || filePath.length() == 0) {
            return false;
        }

        File file = new File(filePath);

        boolean bWrite = false;

        FileOutputStream fileOutputStream = null;
        OutputStreamWriter outputStreamWriter = null;
        BufferedWriter bufferedWriter = null;

        try {
            fileOutputStream = new FileOutputStream(file, bAppend);
            outputStreamWriter = new OutputStreamWriter(fileOutputStream, “UTF-8”);
            bufferedWriter = new BufferedWriter(outputStreamWriter);

            if (stringList != null && stringList.size() > 0) {
                String oneLine = null;

                int lineCount = stringList.size();
                int lastIndex = lineCount – 1;

                for (int i = 0; i < lineCount; i++) {
                    oneLine = stringList.get(i);

                    bufferedWriter.write(oneLine, 0, oneLine.length());
                    if (i < lastIndex) {
                        bufferedWriter.newLine();
                    }
                }
            }

            bWrite = true;

        } catch (IOException e) {
            throw e;

        } catch (Exception e) {
            throw e;

        } finally {
            close(bufferedWriter);
            close(outputStreamWriter);
            close(fileOutputStream);
        }

        return bWrite;
    }

    
    private static void close(BufferedWriter bufferedWriter) {
        try {
            if (bufferedWriter != null) {
                bufferedWriter.close();
            }
        } catch (Exception e) {
            // 무시

        } finally {
            bufferedWriter = null;
        }
    }
    
    
    private static void close(OutputStreamWriter outputStreamWriter) {
        try {
            if (outputStreamWriter != null) {
                outputStreamWriter.close();
            }
        } catch (Exception e) {
            // 무시

        } finally {
            outputStreamWriter = null;
        }
    }
    
    
    private static void close(FileOutputStream fileOutputStream) {
        try {
            if (fileOutputStream != null) {
                fileOutputStream.close();
            }
        } catch (Exception e) {
            // 무시

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

        } finally {
            fileInputStream = null;
        }
    }

    
    private static void close(InputStreamReader inputStreamReader) {
        try {
            if (inputStreamReader != null) {
                inputStreamReader.close();
            }
        } catch (Exception e) {
            // 무시

        } finally {
            inputStreamReader = null;
        }
    }
    
    
    private static void close(BufferedReader bufferedReader) {

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


        } finally {
            bufferedReader = null;
        }
    } 

Winmerge 트리로 보기

Winmerge 트리로 보기

윈머지 폴더 비교(윈머지 디렉토리 비교)시 트리 형태로 보고 싶다면,

상단 메뉴의 [보기] – [Tree Mode] 를 클릭하면 된다.

 

만약, Tree Mode 가 비활성화 상태라면 아래와 같이 디렉토리를 선택할 때 [하위 디렉토리 포함] 항목을 체크해서 열기한다.


참고 : http://egloos.zum.com/tactlee/v/2780674

[JAVA] 자바 2D 게임 만들기 단상 (JFrame drawImage 속도 빠르게)

[JAVA] 자바 2D 게임 만들기 단상 (JFrame drawImage 속도 빠르게)

퓨어 자바로 게임을 짤 때마다 속도가 너무 느려서 좌절하곤 했었다.

원어로 된 관련 서적을 사서 읽어보기도 했지만, 핵심적인 부분을 찾아내지 못했고, 결국 포기했었다.

책에는 분명 퓨어 자바도 빠르게 그림을 찍어낼 수 있다고 쓰여있었는데 말이다.

각설하고, 이제 방법을 알아냈다.

알다시피 2D 게임을 만드는 가장 일반적인 방법은, 루프를 걸어놓고 그림 1장만 계속 찍어내는 것이다.

만약 800 x 600 해상도 게임을 만든다면, 가장 속도가 빠른 방법은 800 x 600 그림 1장을 반복적으로 찍어내는 것이다.

왜? 캐릭터, 타일 등을 곧바로 화면에 찍으면 느리다.

그러므로 800 x 600 짜리 이미지 객체를 만들어놓고, 타일이나 캐릭터 등을 그 이미지 객체에 찍는다.

그리고 완성된 이미지 객체를 화면에 찍는 것이다.

이렇게 해야 화면에 찍는 횟수를 최소화할 수 있다.

코드를 간략히 만들면, 아래와 같은 방식이다.

// 게임루프

while (true) {

    jFrame.getGraphics().drawImage(mainImg, 0, 0, null);

}

여기서 mainImg는 BufferedImage 객체다.

나는 여태까지 BufferedImage 객체이면 다 똑같은 객체인줄 알았다.

그런데 BufferedImage 객체는, 타입값에 따라 내부적으로 다르게 구성되는 특징이 있었고, 이게 키 포인트였다.

여태까지 나는 BufferedImage 의 픽셀을 수정하기 위해서 setRGB 메서드를 썼다.

가로 800, 세로 600 의 이중포문(for)을 돌면서 setRGB 메서드로 이미지 객체를 변경했는데,

이 setRGB 메서드가 엄청나게 느린 것이었다.

setRGB 메서드는 무한루프 안에서 사용하면 안되는 메서드였다.

하지만, 여태까지는 다른 대안을 몰라서 사용할 수 밖에 없었다.

for (int x=0; x<800; x++) {

    for (int y=0; y<600; y++) {

        // setRGB 메서드는 아주 느리다. 사용금지.

        mainImg.setRGB(x, y, rgbValue);

    }

}

그렇다면 다른 방법은 무엇일까?

우선 이미지를 만들 때 아래와 같이 만든다.

BufferedImage mainImg = new BufferedImage(800, 600, BufferedImage.TYPE_INT_RGB);

그러면 아래와 같이 int 배열을 뽑아낼 수가 있다.

int[] pixels = ((DataBufferInt) mainImg.getRaster().getDataBuffer()).getData();

RGB값을 아래와 같이 변경할 수 있다.

단순히 배열의 int 값을 바꾸면 된다.

// pixels.length == 480000 이다. 800 x 600 과 같다.

int len = pixels.length;

for (int i = 0; i < len; i++){
    pixels[i] = rgbValue;
}

이것이 중요한 이유는, setRGB 메서드보다 훨씬 빠른 속도로 RGB 값을 교체할 수 있기 때문이다.

(훨씬 빠르다는 표현이 부족할 정도로, 비교할 수 없을 정도로 큰 속도 차이가 난다. 이 코드 1줄로 인해 자바 2D 게임을 만들 수 있느냐 아니냐가 결정난다.)

마지막으로, int 값을 수정하기 쉽게 하려면, 게임 내에서 사용해야 하는 이미지들을 미리 읽어와야 한다.

게임루프에 들어가기 전 그림을 읽어와야 하는데, 이 때 BufferedImage.TYPE_INT_RGB 로 이미지를 읽어와야 한다.

그림파일을 BufferedImage 객체로 읽어오는 코드는 원래는 다음과 같이 쓴다.

public static BufferedImage loadImage(String filePath) throws Exception {

    BufferedImage image = null;

    File imgFile = new File(filePath);
    if (!imgFile.exists()) {
        return null;
    }

    image = ImageIO.read(imgFile);
    return image;
}

이 코드의 문제는, BufferedImage 객체를 읽어온 후 ((DataBufferInt) image.getRaster().getDataBuffer()).getData(); 코드를 적용하면 오류가 발생한다는 점이다.

클래스 캐스트 익셉션이 발생한다. (java.lang.ClassCastException: java.awt.image.DataBufferByte cannot be cast to java.awt.image.DataBufferInt)

그러므로 미리 그림을 읽어오는 시점에 TYPE_INT_RGB 타입으로 저장하도록 아래처럼 코딩을 하자.

public static BufferedImage loadImageToRGB(String filePath) throws Exception {

    BufferedImage image = null;

    File imgFile = new File(filePath);
    if (!imgFile.exists()) {
        return null;
    }

    image = ImageIO.read(imgFile);
  
    int width = image.getWidth();
    int height = image.getHeight();
    BufferedImage rgbImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
    for (int x=0; x<width; x++) {
        for (int y=0; y<height; y++) {
            rgbImage.setRGB(x, y, image.getRGB(x, y));
        }
    }
  
     return rgbImage;
}

이렇게 모든 그림파일들을 TYPE_INT_RGB 타입의 BufferedImage 객체로 만들어놓고, 화면에 찍히는 메인 이미지의 int 배열의 값을 교체하는 방식이라면 간단한 2D 게임을 돌리는 데에는 충분한 속도가 나올 것이다.

전체 코드는 다음 포스트를 참고하자.

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

[JAVA] (펌)자바 게임 제작 예제

[JAVA] (펌)자바 게임 제작 예제
짧지만 정말 강력한 코드.
책에는 퓨어 자바로 게임을 만들어도 빠르다던데, 왜 내가 짠 게임은 느릴까 늘 좌절해왔다.
알고 보니 딱 한 가지 포인트였다. 허탈하지만 이제라도 알게 되어서 다행이다.
* 예제코드는 아래 유튜브의 내용을 그대로 퍼온 것임.

package ca.vanzenben.game;

import java.awt.BorderLayout;
import java.awt.Canvas;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.image.BufferStrategy;
import java.awt.image.BufferedImage;
import java.awt.image.DataBufferInt;

import javax.swing.JFrame;

public class Game extends Canvas implements Runnable {

    private static final long serialVersionUID = 1L;

    public static final int WIDTH = 160;
    public static final int HEIGHT = WIDTH / 12 * 9;
    public static final int SCALE = 3;
    public static final String NAME = “Game”;

    private JFrame frame;

    public boolean running = false;
    public int tickCount = 0;

    private BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
    private int[] pixels = ((DataBufferInt) image.getRaster().getDataBuffer()).getData();

    public Game() {
        setMinimumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
        setMaximumSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));
        setPreferredSize(new Dimension(WIDTH * SCALE, HEIGHT * SCALE));

        frame = new JFrame(NAME);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLayout(new BorderLayout());

        frame.add(this, BorderLayout.CENTER);
        frame.pack();

        frame.setResizable(false);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public synchronized void start() {
        running = true;
        new Thread(this).start();
    }

    public synchronized void stop() {
        running = false;
    }

    public void run() {
        long lastTime = System.nanoTime();
        double nsPerTick = 1000000000D / 60D;

        int frames = 0;
        int ticks = 0;

        long lastTimer = System.currentTimeMillis();
        double delta = 0;

        while (running) {
            long now = System.nanoTime();
            delta += (now – lastTime) / nsPerTick;
            lastTime = now;
            boolean shouldRender = true;
            while (delta >= 1) {
                ticks++;
                tick();
                delta -= 1;
                shouldRender = true;
            }

            try {
                Thread.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if (shouldRender) {
                frames++;
                render();
            }

            if (System.currentTimeMillis() – lastTimer >= 1000) {
                lastTimer += 1000;
                System.out.println(ticks + ” ticks, ” + frames + ” frames”);
                frames = 0;
                ticks = 0;
            }
        }
    }

    public void tick() {
        tickCount++;

        for (int i = 0; i < pixels.length; i++) {
            pixels[i] = i + tickCount;
        }
    }

    public void render() {
        BufferStrategy bs = getBufferStrategy();
        if (bs == null) {
            createBufferStrategy(3);
            return;
        }

        Graphics g = bs.getDrawGraphics();

        g.setColor(Color.BLACK);
        g.fillRect(0, 0, getWidth(), getHeight());

        g.drawImage(image, 0, 0, getWidth(), getHeight(), null);

        g.dispose();
        bs.show();
    }

    public static void main(String[] args) {
        new Game().start();
    }
}

 

[Android] 안드로이드 스튜디오 Intel HAXM is required to run this AVD

[Android] 안드로이드 스튜디오 Intel HAXM is required to run this AVD

안드로이드 스튜디오에서 Run app 시도했을 때, 가상기기(AVD, Android Virtual Device)를 선택했을 때 아래 메시지가 나오는 경우 해결 방법.

Intel HAXM is required to run this AVD.
VT-x is disabled in BIOS.

Enable VT-x in your BIOS security settings (refer to documentation for your computer).

말 그대로 바이오스 보안 설정에 들어가서 VT-x 를 활성화 시키면 된다.

1. 바이오스 모드로 진입


1-1. 단축키 사용하여 바이오스 모드 진입

컴퓨터 부팅 시점에 F10 키를 연타하면 바이오스 모드로 진입하자. (컴퓨터마다 다름)

1-2. 윈도우 10 고급 시작 옵션으로 바이오스 모드 진입

윈도우 10 이라면 아래 방법이 더 쉬울 수도 있다.

(1) 윈도우 10 기준, 화면 좌측 하단의 윈도우 버튼을 클릭 -> “고급 시작” 문자열 검색 -> [고급 시작 옵션 변경] 메뉴 클릭

 

(2) 복구 탭에서 [지금  다시 시작] 버튼 클릭

 

(3) [옵션 선택] 화면에서 [문제 해결] 항목 클릭

 

(4) [문제 해결] 화면에서 [고급 옵션] 항목 클릭

 

(5) [고급 옵션] 화면에서 [UEFI 펌웨어 설정] 항목 클릭

 

(6) [UEFI 펌웨어 설정] 화면에서 [다시 시작] 항목 클릭


컴퓨터마다 바이오스 진입 단축키 및 화면이 다를 수 있다.

위 방법으로 안된다면, 회사별 바이오스 모드 부팅 방법을 검색하자.

2. 바이오스 모드에서 VTx 기능 활성화

운영체제 마다 바이오스 모드 화면이 다른데, HP의 경우 아래 화면처럼 설정하면 된다.

(출처 : https://support.hp.com/kr-ko/document/c04773339 / HP 워크스테이션 PC – BIOS에서 가상화 기술을 활성화 또는 비활성화)

2-1. 상단의 Security 메뉴 선택 – System Security 선택 – Virtualization Technology (VTx) 상태를 Enabled 로 변경 – 변경사항 저장(Save Changes) 후 바이오스 종료



위의 화면은 HP 웹사이트에서 제공한 스크린샷이다.

참고로 필자의 컴퓨터에서는 화면이 다르게 나와서 아래와 같이 진행하였다.

2-2. 상단의 Advanced 메뉴 선택 – System Options 선택 – Virtualization Technology (VTx) 체크 – 변경사항 저장(Save Changes) 후 바이오스 종료



3. 안드로이드 스튜디오에서 Run app


[sqlplus] SELECT 예제 (sqlplus EOF 사용)

[sqlplus] SELECT 예제 (sqlplus EOF 사용)

sqlplus 아이디/비번 << EOF
set feedback off
set linesize 150
col “Tablespace Name” format a20
col “Bytes(MB)”       format 999,999,999.99
col “Used(MB)”        format 999,999,999.99
col “Percent(%)”      format 999999.99
col “Free(MB)”        format 999,999,999.99
col “Free(%)”         format 9999.99
select ddf.tablespace_name “Tablespace Name”,
       ddf.bytes/1024/1024 “Bytes(MB)”,
       (ddf.bytes – dfs.bytes)/1024/1024 “Used(MB)”,
       round(((ddf.bytes – dfs.bytes) / ddf.bytes) * 100, 2) “Percent(%)”,
       dfs.bytes/1024/1024 “Free(MB)”,
       round((1 – ((ddf.bytes – dfs.bytes) / ddf.bytes)) * 100, 2) “Free(%)”
from   (select tablespace_name, sum(bytes) bytes
        from   dba_data_files
        group by tablespace_name) ddf,
       (select tablespace_name, sum(bytes) bytes
        from   dba_free_space
        group by tablespace_name) dfs
where   ddf.tablespace_name = dfs.tablespace_name and dfs.tablespace_name not like ‘TS%’
order by ((ddf.bytes-dfs.bytes)/ddf.bytes) desc
EOF

[Android] jks 파일 패스워드 분실 찾아내는 법

[Android] jks 파일 패스워드 분실 찾아내는 법

이번에 안드로이드를 하게 되면서 jks 인증서 파일에 대해 알게 되었는데,

비밀번호를 분실했을 경우 추출해낼 수 있는 방법이 없다.

단방향 암호화로 패스워드를 저장하기 때문이다.

그러던 와중 아래 프로그램을 발견했다.

<Android-keystore-password-recover>

엄청난 프로그램이다.

Bruteforce로 알파벳과 특수문자를 순서대로 때려넣어서 jks 파일의 패스워드를 찾을 때까지 시도한다.

http://maxcamillo.github.io/android-keystore-password-recover/

참고로 jks 파일의 내용을 보게 해주는 크랙은 다음 주소를 참고하면 된다.

plain text를 볼 수는 없고 암호화된 값까지만 볼 수 있지만, 인증서 구조를 뜯어보고 싶다면 필수일 것이다.

https://github.com/floyd-fuh/JKS-private-key-cracker-hashcat

[IOS] 인증서가 유효하지 않기 때문에 응용 프로그램을 설치할 수 없습니다

[IOS] 인증서가 유효하지 않기 때문에 응용 프로그램을 설치할 수 없습니다

iOS7.1 이상에서 다운로드 링크를 https 가 아닌 http 로 제공했을 경우, <인증서가 유효하지 않기 때문에 응용 프로그램을 설치할 수 없습니다> 오류가 발생할 수 있다.

<a href=”itms-services://?action=download-manifest&url=http://다운로드링크주소“>download</a> 를

<a href=”itms-services://?action=download-manifest&url=https://다운로드링크주소“>download</a> 로 변경해야 한다.

만약 https 서버를 갖고 있지 않을 경우, 드랍박스(dropbox) 사이트를 이용할 수 있다.

ex) https://dl.dropboxusercontent.com/s/abcdefghijk/test.plist 또는 https://dl.dropboxusercontent.com/s/abcdefghijk/test.ipa

드랍박스(dropbox) 파일 다운로드 링크를 만드는 방법은 아래 포스트를 참고.

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

드랍박스(dropbox) 파일 다운로드 주소 만드는 방법

드랍박스(dropbox)  파일 다운로드 주소 만드는 방법

1. https://www.dropbox.com/ 접속
2. 우측 상단 [로그인] 클릭

 

3. 좌측메뉴의 [파일] 클릭

4. 목록상 원하는 파일 위에서 [공유] 콤보박스 화살표 클릭 – [이메일을 통해 초대] 클릭

 

5. 이메일 윈도우 하단의 [링크 복사] 클릭

 

6. 링크 복사를 클릭해서 표시되는 경로는 다음 형식을 갖는다.
https://www.dropbox.com/s/abcdefghijk/test.plist?dl=0

(1) 주소 뒤쪽의 “?dl=0” 을 제거한다.
(2) 주소 앞쪽의 “https://www.dropbox.com/” 부분을
https://dl.dropboxusercontent.com/” 로 수정하면 곧바로 다운로드 받을 수 있는 주소가 된다.

ex) https://www.dropbox.com/s/abcdefghijk/test.plist?dl=0
=> https://dl.dropboxusercontent.com/s/abcdefghijk/test.plist
 

[Mac] 맥에서 SVN 사용하기 (SnailSVN Lite)

[Mac] 맥에서 SVN 사용하기 (SnailSVN Lite)

원래는 Xcode 에 SVN 플러그인을 붙여서 사용하고 싶었다.

찾아봤더니 최신  Xcode 에서는 Git 은 지원해도 SVN 은 더 이상 지원하지 않았다.

(역시 대세는 Git 이다. 하지만 난 회사 업무 때문에 SVN 을 써야 한다)

결국 SVN 툴을 써야만 하는 상황이 왔다.

각설하고, 윈도우의 tortoiseSVN 와 비슷하다는 SnailSVN 을 사용해보았다.

사용하기 쉽다.

0. 전제조건

SVN 서버가 이미 깔려있음을 전제로 한다.

1. 앱 스토어 열기

맥 화면 하단의 독(Dock) 에서 앱 스토어(아이콘 색이 파란색이며 알파벳 A가 그려져 있는 아이콘)을 클릭한다.

2. SnailSVN 검색

유료 버전이 아닌 무료 버전이 있다. 정식명칭은 SnailSVN Lite  이다.

3. 설치 진행

3-1. SVN 버전 선택

필자는 SVN 버전을 1.9 로 설정했다.

설치되어 있는 SVN 버전에 맞춰서 깔면 되고, 나중에 환경설정으로 수정할 수도 있다.



3-2. 아래와 같은 난감한 화면이 나오는데, 그냥 [Next] 버튼을 눌러 진행했다.




4. 설치 완료

5. SnailSVN 실행

설치 완료 이후 자동으로 실행될 것이다. 그렇지 않다면 응용프로그램 목록에서 찾아서 실행하자.

6. 실행 윈도우 하단의 [Open System Preferences] 버튼을 클릭하면, 아래 화면이 나온다.




좌측 목록에서 [Finder] 클릭.

우측 목록에서 [SnailSVN Lite Extension] 을 찾아서 체크 표시한다.

모든 설치 및 세팅이 끝났다.

7. 사용 방법

7-1. 체크아웃

체크아웃은 SVN 상에 있는 파일들을 다운로드 받는 것이다.

[Finder] (윈도우로 치면 일종의 탐색기)를 연다.

적당한 위치에 폴더를 만들어놓고, 우측 상단의 [S] 버튼을 클릭 – SVN Checkout 을 클릭한다.



7-2. 커밋과 업데이트

커밋은 로컬 파일을 SVN 서버에 업로드하는 것이다.

업데이트는 SVN 서버에 있는 파일 기준으로, 변경된 로컬 파일을 다시 내려받는 것이다.

커밋은 SVN 이 연결된 상태의 폴더에서(체크아웃을 수행한 폴더 내에서) 원하는 파일 또는 폴더를 우클릭 – [SVN Commit…] 을 클릭하면 수행할 수 있다.

업데이트도 비슷한 방법으로 수행한다.

마우스 우클릭 후 [SVN Update] 를 클릭하면 된다.

이때 마우스 우클릭을 했는데도 커밋과 롤백 버튼이 나오지 않는 경우가 있다.

SnailSVN을 실행하면 SnailSVN Lite Preferences 윈도우가 뜬다. 상단메뉴에 폴더 모양 아이콘이 있고 Working Copies라고 되어있는데 여기에 워킹 카피를 하나 등록해야 그 위치에서 커밋과 롤백이 가능하다.

무료버전은 한 개까지 워킹 카피를 등록할 수 있다. 따라서 또다른 SVN 리파지토리를 바라볼 때는 워킹 카피를 바꿔가며 사용해야 한다.

 


 참고사이트) https://eteris.tistory.com/1242

[Mac] 맥 USB 안전하게 제거 (추출)

[Mac] 맥 USB 안전하게 제거 (추출)

맥에서 연결한 외장하드 또는 USB 를 제거하는 방법이다.

Finder 프로그램(윈도우로 치면 일종의 탐색기) 에서 해당 기기를 찾고, 마우스 우클릭하여 [‘디스크이름’ 추출] 을 선택하면 된다.

 

[IOS] Hello World 프로그램 작성 (Xcode / Objective-C)

[IOS] Hello World 프로그램 작성 (Xcode / Objective-C)

0. 맥이 있어야 하고, Xcode가 설치되어 있어야 한다.

Xcode 설치 방법은 다음 글에서 확인할 수 있다.

[IOS] 맥 Xcode 설치 : https://blog.naver.com/bb_/221577128641

1. Xcode 프로그램 기동
2. [Create a new Xcode project] 를 클릭




3. [Single View App] 선택




4. Product Name 은 HelloWorld. 나머지 항목은 적당히 써넣는다.

Language 는 Objective-C 선택.

[Next] 버튼을 클릭하면 워크스페이스 위치를 잡아줄텐데 적당한 위치로 지정하자. (ex: Documents 폴더의 IOS_Workspaces/HelloWorld)




5. 프로젝트가 생성되었다.



6. 좌측 트리에서 Main.storyboard 를 더블클릭한다. 화면이 보일 것이다.



7. 상단 메뉴의 [View] – [Libraries] – [Show Library] 클릭



8. 라이브러리 창이 뜨면 상단 검색창에 button 을 검색한다.

검색결과로 나온 button을 Main.storyboard 화면 중앙 쯤에 드래그앤드랍으로 끌어다 놓는다.




9. 만들어진 버튼 우클릭하여 [Touch Up Inside] 를 찾는다.

여기가 생각보다 어려운데, [Touch Up Inside] 우측의 동그란 라디오 버튼을 클릭한 채로 드래그를 하면 파란 선이 나온다.

이 파란 선을 ViewController.h 파일 @interface 와 @end 사이에 끌어다 놓는다.

버튼이 그려져 있는 화면과 ViewController.h 파일의 내용을 나란히 볼 수 있도록 조정하는게 관건이다.

(필자의 경우 폴더 트리에서 파일 위 마우스 우클릭 – [Open in New Window] 를 이용해서, 화면 2개를 나란히 띄울 수 있었음)

10. 창이 하나 뜨면 [Name] 항목에 [Button] 이라고 입력하고 OK한다.

11. 이후 ViewController.h 파일에는

– (IBAction)Button:(id)sender;

코드가 내용으로 들어가 있을 것이다.

(물론 ViewController.m 파일 내용도 변경되어 있음)

12. 이제 ViewController.m 파일의 내용을 수정한다.

 



[AS-IS]

– (IBAction)Button:(id)sender {

}

[TO-BE]

– (IBAction)Button:(id)sender {

    NSLog(@”Button Touched”);

    

    UIAlertView *alert = [[UIAlertView alloc]

                          initWithTitle:@”alert”

                          message:@”Hello World”

                          delegate:nil

                          cancelButtonTitle:nil

                          otherButtonTitles:@”OK”, nil];

    

    [alert show];

}

13. 테스트한다. 폰 버전은 원하는 버전(ex : iPhone7) 으로 맞추고, 재생 버튼을 누른다.






버튼을 클릭하면 Hello World 가 표시된다.

[IOS] 맥 Xcode 설치

[IOS] 맥 Xcode 설치

1. Xcode 설치

* Xcode 는 맥 OS 에만 설치할 수 있다.

* Xcode 는 맥 프로그램 또는 IOS(아이폰) 프로그램을 개발하기 위한 프로그램이다.

* 이미 Xcode 가 설치되어 있는 경우 이 항목은 건너뛰면 된다.

1-1. 하단 독(Dock)에서 앱 스토어(파란색 배경에 알파벳 A 모양이 그려져 있는 아이콘)를 클릭한다.



1-2. 우측 상단 검색창에 Xcode 를 검색한다.




1-3. Xcode [받기] – [앱 설치] 버튼을 차례로 눌러 설치한다.




여기까지 문제가 없으면 성공이다.

2. 문제해결

필자는 문제가 있었다.

[구매를 완료할 수 없습니다. macOS 버전 10.14.3 이상이 필요하기 때문에 ‘무제’에 Xcode을(를) 설치할 수 없습니다.] 라는 메시지가 떴다.

알아보니 맥 OS 버전을 최신으로 업데이트하면 

검색결과 [좌측 최상단의 사과 아이콘] – [시스템 환경설정] 에서 업데이트를 진행할 수 있다고 했다.

그런데 현재 OS 버전이 낮아서 그런 항목을 찾을 수 없었고, 현재 OS 버전에 맞춰 Xcode를 구하기로 했다.

현재 필자의 맥 OS 버전은 10.13.6 버전이었다.

운영체제 버전에 맞춰, Xcode 를 10.1 버전으로 구하기로 했다.

Xcode 이전 버전은 아래 주소에서 구했다.

Apple 개발자 다운로드 페이지 : https://developer.apple.com/download/more/

위 주소로 접속하여, 좌측 인풋박스에 [Xcode]를 입력하고 검색, 우측 목록을 살펴보았더니 Xcode 10.1 버전이 있었다.
용량이 6기가여서 시간이 조금 걸렸다.
Xcode_10.1.xip 파일이 [다운로드] 폴더에 들어왔다. 
[열기] 해보니 압축이 해제되었다.
아래 스크린샷은 압축 해제된 이후의 모습이다.
Xcode를 더블클릭하자 잘 실행되었다.
아래 화면처럼 나오면 성공이다.
버전 정보도 잘 나온다.

[javascript] js base64 encode / js base64 decode

[javascript] js base64 encode / js base64 decode

크롬에서는 js base64 인코딩 시 btoa 함수를 사용, js base64 디코딩시 atob 함수를 사용하면 된다.

그런데 IE에서는 해당 함수가 구현되어 있지 않다.

base64 인코딩이나 디코딩을 하는 jsp 또는 html 페이지에 다음 js를 임포트하여 btoa, atob 함수를 구현한다.

base64.js (첨부된 base64_js.txt 를 다운로드 받아 확장자 변경할 것)

 (function() {
    window.btoa || (
    window.btoa = function(input) {
        var chars = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’;
        var str = String (input);
        for (var block, charCode, idx = 0, map = chars, output = ”;
            str.charAt (idx | 0) || (map = ‘=’, idx % 1);
            output += map.charAt (63 & block >> 8 – idx % 1 * 8)) {
            charCode = str.charCodeAt (idx += 3 / 4);
            if (charCode > 0xFF) {
                alert(“btoa failed : The string to be encoded contains characters outside of the Latin1 range.”);
                return input;
            }
            block = block << 8 | charCode;
        }
        return output;
    });

    window.atob || (
    window.atob = function(input) {
        var chars = ‘ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=’;
        var str = (String (input)).replace (/[=]+$/, ”);
        if (str.length % 4 === 1) {
            alert(“atob failed : The string to be decoded is not correctly encoded.”);
            return input;
        }
        for (var bc = 0, bs, buffer, idx = 0, output = ”;
            buffer = str.charAt (idx++);
            ~buffer && (bs = bc % 4 ? bs * 64 + buffer : buffer, bc++ % 4) ? output += String.fromCharCode (255 & bs >> (-2 * bc & 6)) : 0) {
            buffer = chars.indexOf (buffer);
        }
        return output;
    });
} ());

함수 사용은 아래와 같이 한다. (참고사이트 2 읽어보기)

function encodeBase64(_str) {
    return btoa(encodeURIComponent(_str));
}
   
function decodeBase64(_str) {
    return decodeURIComponent(atob(_str));
}

마지막 팁. 만약 base64 인코딩한 값을 특정 URL에 파라미터로 붙여 보내야 한다면(get 방식), encodeBase64 한 결과에 encodeURIComponent 를 씌워주는 것이 안전하다.

ex)  var encParam1 = encodeBase64(originParam1);

var targetUrl = “프로토콜://도메인명?param1= + encodeURIComponent(encParam1)

2020/08/03 내용추가

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

참고사이트 1) https://coderanch.com/t/630782/languages/window-btoa-supported

참고사이트 2) https://stackoverflow.com/questions/30631927/converting-to-base64-in-javascript-without-deprecated-escape-call 

[Eclipse] 이클립스 실행시 기본 워크스페이스 지정 (이클립스 바로가기)

[Eclipse] 이클립스 실행시 기본 워크스페이스 지정 (이클립스 바로가기)

1. 이클립스 바로가기 아이콘을 만든다.

2  바로가기 아이콘 위에서 마우스 우클릭 – [속성(R)]

3. [대상(T)] 인풋박스에 이클립스 exe 파일 경로가 들어있을 것이다. 그 뒤에 ” -data 프로젝트 폴더명”을 붙여준다.

ex)

[AS-IS] C:\eclipse_x64\eclipse.exe

[TO-BE] C:\eclipse_x64\eclipse.exe -data “C:workspaces\프로젝트_폴더명”

이제 이클립스가 기동될 때 지정한 프로젝트가 기본으로 열릴 것이다.

바로가기를 n개 만들어놓고 각각 적당한 이름과 경로를 할당하면 사용하기 편하다.

[Android] 안드로이드 스튜디오(Android Studio) 에서 SVN 사용

[Android] 안드로이드 스튜디오(Android Studio) 에서 SVN 사용

1. 안트로이드 스튜디오 설치

참고로 필자의 안드로이드 스튜디오 버전은 Android Studio 3.4.1이다.

2. Apache-Subversion-1.11.1 다운로드

Apache-Subversion-1.11.1을 다운로드하고 적당한 위치에 압축을 풀어놓는다.

ex) C:\Apache-Subversion-1.11.1

 

다운로드는 아래 주소에서 할 수 있다.

https://www.apache.org/dist/subversion/

3. 환경설정에 Apache Subversion 실행파일 위치 설정

3-1. 안드로이드 스튜디오 실행 – 상단 메뉴의 [File] – [Settings]

 

3-2. [Settings] 창에서 – 좌측메뉴의 [Version Control] – [Subversion] 클릭 – 화면 상단 인풋박스에 svn.exe 파일 경로를 기입한다.

ex) C:\Apache-Subversion-1.11.1\bin\svn.exe


4. SVN 레파지토리로 새 프로젝트 열기

상단 메뉴의 [File] – [New] – [Project from Version Control] – [Subversion] 클릭


5. SVN 레파지토리 추가 및 체크아웃

상단 [+] 버튼으로 SVN 레파지토리를 추가할 수 있다.

해당 SVN 레파지토리를 마우스 우클릭 – [Checkout] 할 수 있다.


6. 체크아웃 위치 지정

우측의 […] 버튼을 클릭하여 Destination 을 원하는 위치로 조정한다.

OK를 클릭하면 SVN 레파지토리와 연결된 프로젝트가 생성된다. 

[JAVA] 자바 SVN 이력 / get SVN history example / get svn change log example

[JAVA] 자바 SVN 이력 / get SVN history example / get svn change log example 

자바 코드로 SVN 히스토리를 가져오는 방법.

구글링이 쉽지 않았으나 결국 찾아냈다.

1. 라이브러리 다운로드

라이브러리는 svnkit-1.9.3.jar 파일을 사용하였다.

아래 주소에서 다운로드받을 수 있다.

https://mvnrepository.com/artifact/org.tmatesoft.svnkit/svnkit/1.9.3

2. SVN Change Log 가져오기 예제

public static void printSVNChangeLog() throws Exception {

        String url = “svn://실제_접속가능한_SVN주소를_입력하기”;
        // String svnUser = “”;
        // String svnPassword = “”;
       
        SVNURL svnUrl = SVNURL.parseURIDecoded(url);
        SVNRepository svnRepo = SVNRepositoryFactory.create(svnUrl);

        // BasicAuthenticationManager authManager = new BasicAuthenticationManager(svnUser, svnPassword);
        // svnRepo.setAuthenticationManager(authManager);

        long latestRevision = svnRepo.getLatestRevision();
        System.out.println(“latestRevision : ” + latestRevision);

        long startRevision = latestRevision;
        long endRevision = latestRevision;

        Collection<SVNLogEntry> logEntries = null;
        logEntries = svnRepo.log(new String[] {“”}, null, startRevision, endRevision, true, true);

        Iterator entries = logEntries.iterator();
        while (entries.hasNext()) {
            SVNLogEntry logEntry = (SVNLogEntry) entries.next();
            if (logEntry == null) {
                continue;
            }
           
            System.out.println(“———————————————“);
            System.out.println(“revision: ” + logEntry.getRevision());
            System.out.println(“author: ” + logEntry.getAuthor());
            System.out.println(“date: ” + logEntry.getDate());
            System.out.println(“log message: ” + logEntry.getMessage());
           
            if (logEntry.getChangedPaths() == null || logEntry.getChangedPaths().size() == 0) {
                continue;
            }

            System.out.println();
            System.out.println(“changed paths:”);
            Set changedPathsSet = logEntry.getChangedPaths().keySet();
            Iterator changedPaths = changedPathsSet.iterator();
            while (changedPaths.hasNext()) {
                SVNLogEntryPath entryPath = (SVNLogEntryPath) logEntry.getChangedPaths().get(changedPaths.next());
               
                if (entryPath.getCopyPath() != null) {
                    System.out.println(” ” + entryPath.getType() + ” ” + entryPath.getPath() + “(from ” + entryPath.getCopyPath() + ” revision ” + entryPath.getCopyRevision() + “)”);
                } else {
                    System.out.println(” ” + entryPath.getType() + ” ” + entryPath.getPath());
                }
            }
        }
    }

특정 SVN 주소에 대해서 startRevision 부터 endRevision 까지 커밋 이력을 출력한다.

예를 들어 startRevision = 1 이고 endRevision = 10 이면 1부터 10을 순서대로 모두 출력한다.

현재 코드는 가장 최신 리비전 이력만 출력한다.

위 코드에서는 권한 문제가 발생하지 않아서 SVN 유저 이름과 패스워드를 입력하지 않았는데(해당 부분 주석처리함),

필요하다면 주석처리를 풀면 된다.

결과는 아래와 같이 나온다.

아래 주소를 참고하여 작성했다.

참고페이지) https://wiki.svnkit.com/Printing_Out_Repository_History

3. SVN 특정 리비전의 파일 내용 가져오기 예제

public static void getSVNFileContentToFile(SVNRepository svnRepo, long revision, String path) {
        FileOutputStream outputStream = null;
        File file = null;
       
        try {
            file = new File(“temp.txt”);
            if (file.exists()) {
                file.delete();
                file.createNewFile();
            } else {
                file.createNewFile();
            }
           
            outputStream = new FileOutputStream(file);
            // svnRepo.getFile(path, SVNRevision.HEAD.getNumber(), new SVNProperties(), outputStream);
            svnRepo.getFile(path, revision, new SVNProperties(), outputStream);
           
        } catch (Exception e) {
            e.printStackTrace();
           
        } finally {
            flush(outputStream);
            close(outputStream);
        }
    }
   
   
    public static void getSVNFileContent(SVNRepository svnRepo, long revision, String path) {
        ByteArrayOutputStream outputStream = null;
       
        try {
            outputStream = new ByteArrayOutputStream();
            // svnRepo.getFile(path, SVNRevision.HEAD.getNumber(), new SVNProperties(), outputStream);
            svnRepo.getFile(path, revision, new SVNProperties(), outputStream);
           
            System.out.println(outputStream.toString());
           
           
        } catch (Exception e) {
            e.printStackTrace();
           
        } finally {
            flush(outputStream);
            close(outputStream);
        }
    }
   
   
    public static void flush(OutputStream outputStream) {
        try {
            if (outputStream != null) {
                outputStream.flush();
            }
        } catch (Exception e) {
            // ignore
        }
    }
   
   
    public static void close(OutputStream outputStream) {
        try {
            if (outputStream != null) {
                outputStream.close();
            }
        } catch (Exception e) {
            // ignore
        }
    }

특정 리버전에 해당하는 파일 내용을 가져온다.

파일로 저장하려면 getSVNFileContentToFile 메서드를 사용하면 된다. 코드상 temp.txt 에 내용을 쓴다.

스트링으로 저장하려면 getSVNFileContent 메서드를 사용하면 된다.

HTML table td 대각선

HTML table td 대각선

HTML 테이블에 대각선 넣는 방법.

td 의 배경이미지로 대각선 그림을 넣으면 된다.

background-image: url(‘/그림경로/slash.png’);

background-size: 100% 100%;

background-repeat: no-repeat;


 

출처: https://zetawiki.com/wiki/HTML_table_%EB%8C%80%EA%B0%81%EC%84%A0_%EA%B7%B8%EB%A6%AC%EA%B8%B0

[Windows] 윈도우 심볼릭 링크

[Windows] 윈도우 심볼릭 링크

윈도우에서도 심볼릭 링크를 만들 수 있다는 걸 최근에야 알게 됐다.

근 1년 동안 알아낸 정보 중에서 단연 최고의 정보였다.

심볼릭 링크란 일종의 가상 폴더다. 실제 대상이 되는 폴더는 다른 곳에 위치해있고, 가상 폴더는 대상 폴더를 가리킬 뿐이다.

파티셔닝이 되어 있는 등의 이유로, C드라이브에 공간이 거의 없고, D드라이브에 공간이 많은 경우 사용할 수 있다.

* C드라이브란?

내 컴퓨터에 들어가면 보이는 “C:”가 C드라이브다. 처음 윈도우를 설치할 때 이 C드라이브를 쪼갤 수 있다(파티셔닝). 예를 들어 3개로 쪼갠다면 C드라이브, D드라이브, E드라이브가 만들어진다. 그런데 각종 프로그램들이 C드라이브에 설치되는 것을 기본으로 하기에, 공간이 모자랄 수 있다. 본 포스트는, 그럴 때 D드라이브와 E드라이브의 공간을 활용할 수 있는 팁이다.

1. D드라이브에 대상 폴더를 만든다.

예를 들면 D:\newfolder\temp 식으로 만든다.

 

2. C드라이브에 똑같은 폴더 구조를 만든다.

예를 들면 C:\newfolder 식으로 만든다.

temp는 가상 폴더로 만들 예정이니, 실제 폴더는 만들지 않는다.

 

3. 관리자 권한으로 cmd 실행 후, <mklink /d 가상폴더 대상폴더> 명령어를 쓰고 엔터키를 누른다.

예를 들어 <mklink /d C:\newfolder\temp D:\newfolder\temp> 명령어를 쓰고 엔터키를 누른다.

4. C:\newfolder\temp 라는 심볼릭 링크가 생성되었다.

C에 있는 것처럼 인식되지만, 실제로는 D에 위치하게 된다.

이렇게 심볼릭 링크를 걸어두면, C드라이브의 용량을 많이 확보할 수 있다.

또한 컴퓨터에서는 여전히 폴더가 C에 위치한다고 인식하고 있어서, 해당 파일을 참조하는 파일이 있어도 전혀 문제가 없다.

Program Files 밑의 응용프로그램을 이동시켰을 때에도 레지스트리 등을 수정할 필요가 없다.