/**
* 초기화 및 기타 작업에 필요한 함수를 제공합니다.
*
* @module koalanlp/Util
* @example
* import * as Util from 'koalanlp/Util';
*/
import * as API from './API';
import {JVM} from './jvm';
import {POS, CoarseEntityType, DependencyTag, PhraseTag, RoleType} from "./types";
import _ from 'underscore';
import {assert, isDefined} from './common';
/**
* @private
* @param api
*/
async function queryVersion(api){
const request = require('request');
let url = `https://repo1.maven.org/maven2/kr/bydelta/koalanlp-${api}/`;
let result = await new Promise((resolve, reject) => {
request(url, {headers: {'User-Agent': 'curl/7.58.0'}}, // Query as if CURL did.
(error, res, body) => {
if(error) reject(error);
else resolve(body);
});
});
let matches = result.match(new RegExp('href="(\\d+\\.\\d+\\.\\d+(-[A-Za-z]+(\\.\\d+)?)?)/"', 'g'));
matches = matches.map((line) => line.substring(6, line.length - 2));
let version = matches.sort().reverse()[0];
console.info(`[INFO] Latest version of kr.bydelta:koalanlp-${api} (${version}) will be used`);
return version;
}
/**
* API와 버전을 받아 Artifact 객체 구성
* @param {API} api 분석기 패키지 이름
* @param {!string} version 버전 또는 LATEST
* @return {{groupId: string, artifactId: string, version: string}} Artifact 객체
* @private
*/
async function makeDependencyItem(api, version){
let isAssembly = API.PACKAGE_REQUIRE_ASSEMBLY.includes(api);
if(typeof version === 'undefined' || version.toUpperCase() === 'LATEST'){
version = await queryVersion(api)
}
let obj = {
"groupId": "kr.bydelta",
"artifactId": `koalanlp-${api}`,
"version": version
};
if(isAssembly){
obj.classifier = "assembly"
}
return obj;
}
/**
* Remote Maven Repository list
* @type {Object[]}
* @private
*/
let remoteRepos = [
{
id: 'sonatype',
url: 'https://oss.sonatype.org/content/repositories/public/'
},
{
id: "jitpack.io",
url: "https://jitpack.io/"
},
{
id: 'jcenter',
url: 'https://jcenter.bintray.com/'
},
{
id: 'maven-central-1',
url: 'https://repo1.maven.org/maven2/'
},
{
id: 'maven-central-2',
url: 'http://insecure.repo1.maven.org/maven2/'
},
{
id: 'kotlin-dev',
url: 'https://dl.bintray.com/kotlin/kotlin-dev/'
}
];
function versionSplit(ver){
let dashAt = ver.indexOf('-');
if (dashAt !== -1){
let semver = ver.substr(0, dashAt).split('\\.');
let tag = ver.substr(dashAt+1);
semver = semver.map(parseInt);
semver.push(tag);
return semver;
}else{
let semver = ver.split('\\.');
return semver.map(parseInt);
}
}
/** @private */
function isRightNewer(ver1, ver2){
let semver1 = versionSplit(ver1);
let semver2 = versionSplit(ver2);
let length = Math.max(semver1.length, semver2.length);
for(let i of _.range(length)){
let comp1 = semver1[i];
let comp2 = semver2[i];
if(!isDefined(comp2)) return true; // 왼쪽은 Tag가 있지만 오른쪽은 없는 상태. (오른쪽이 더 최신)
if(!isDefined(comp1)) return false; // 반대: 왼쪽이 더 최신
if(comp1 !== comp2) return comp1 < comp2; // comp2 가 더 높으면 최신.
}
return false;
}
/**
* 자바 및 의존패키지를 Maven Repository에서 다운받고, 자바 환경을 실행합니다.
*
* @param {Object} options
* @param {Object.<string, string>} options.packages 사용할 패키지와 그 버전들.
* @param {string[]} [options.javaOptions=["-Xmx4g", "-Dfile.encoding=utf-8"]] JVM 초기화 조건
* @param {boolean} [options.verbose=true] 더 상세히 초기화 과정을 보여줄 것인지의 여부.
* @param {!string} [options.tempJsonName='koalanlp.json'] Maven 실행을 위해 임시로 작성할 파일의 이름.
* @example
* import {initialize} from 'koalanlp/Util';
* import {ETRI} from 'koalanlp/API';
*
* // Promise 방식
* let promise = initialize({'packages': {ETRI: '2.0.4'}});
* promise.then(...);
*
* // Async/Await 방식 (async function 내부에서)
* await initialize({ETRI: '2.0.4'});
* ...
*/
export async function initialize(options) {
assert(options.packages, "packages는 설정되어야 하는 값입니다.");
let packages = options.packages;
let verbose = (isDefined(options.verbose)) ? options.verbose : false;
let javaOptions = options.javaOptions || ["-Xmx4g", "-Dfile.encoding=utf-8"];
let tempJsonName = options.tempJsonName || 'koalanlp.json';
/***** 자바 초기화 ******/
let java = require('java');
if (!JVM.canLoadPackages(packages)){
throw Error(`JVM은 두번 이상 초기화될 수 없습니다. ${packages}를 불러오려고 시도했지만 이미 ${JVM.PACKAGES}를 불러온 상태입니다.`);
}
java.options.push(...javaOptions);
java.asyncOptions = {
asyncSuffix: undefined, // Async Callback 무력화
syncSuffix: '', // Synchronized call은 접미사 없음
promiseSuffix: 'Promise', // Promise Callback 설정
promisify: require('util').promisify
};
/***** Maven 설정 *****/
const os = require('os');
const fs = require('fs');
const path = require('path');
const mvn = require('node-java-maven');
// 의존 패키지 목록을 JSON으로 작성하기
let dependencies = await Promise.all(Object.keys(packages)
.map((pack) => makeDependencyItem(API.getPackage(pack), packages[pack])));
// Package 버전 업데이트 (Compatiblity check 위함)
for(const pack of dependencies){
packages[pack.artifactId.replace('koalanlp-','').toUpperCase()] = pack.version;
}
// 저장하기
let packPath = path.join(os.tmpdir(), tempJsonName);
fs.writeFileSync(packPath, JSON.stringify({
java: {
dependencies: dependencies,
exclusions: [
{
groupId: "com.jsuereth",
artifactId: "sbt-pgp"
}
]
}
}));
let threads = require('os').cpus().length;
threads = Math.max(threads - 1, 1);
let promise = new Promise((resolve, reject) => {
mvn({
packageJsonPath: packPath,
debug: verbose,
repositories: remoteRepos,
concurrency: threads
}, function(err, mvnResults) {
if (err) {
console.error('필요한 의존패키지를 전부 다 가져오지는 못했습니다.');
reject(err);
}else {
let cleanClasspath = {};
for(const dependency of Object.values(mvnResults.dependencies)){
let group = dependency.groupId;
let artifact = dependency.artifactId;
let version = dependency.version;
let key = `${group}:${artifact}`;
if(!isDefined(cleanClasspath[key]) || isRightNewer(cleanClasspath[key].version, version)){
cleanClasspath[key] = {
version: version,
path: dependency.jarPath
};
}
}
for(const dependency of Object.values(cleanClasspath)){
if (!isDefined(dependency.path))
continue;
if (verbose)
console.debug(`Classpath에 ${dependency.path} 추가`);
java.classpath.push(path.resolve(dependency.path));
}
JVM.init(java, packages);
// Enum 초기화.
POS.values();
PhraseTag.values();
DependencyTag.values();
RoleType.values();
CoarseEntityType.values();
resolve();
}
});
});
return await promise;
}
/**
* 주어진 문자열 리스트에 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류가 포함되는지 확인합니다.
* @param {string[]} stringList 분류가 포함되는지 확인할 문자열 목록
* @param {(POS|PhraseTag|DependencyTag|CoarseEntityType|RoleType)} tag 포함되는지 확인할 구문분석 표지자/의존구문 표지자/의미역 표지/개체명 분류
* @return {boolean} 포함되면 true.
* @example
* import { contains } from 'koalanlp/Util';
* contains(['S', 'NP'], PhraseTag.NP);
*/
export function contains(stringList, tag) {
if(tag instanceof POS || tag instanceof PhraseTag ||
tag instanceof DependencyTag || tag instanceof RoleType || tag instanceof CoarseEntityType){
return JVM.koalaClassOf('Util').contains(JVM.listOf(stringList), tag.reference);
}else{
return false;
}
}