Skip to content

Commit 138dc84

Browse files
committed
⚡️ Interface構造改革
1 parent 3b0dd6c commit 138dc84

18 files changed

+495
-279
lines changed

dist/JavaLibraryScript.js

Lines changed: 211 additions & 133 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/JavaLibraryScript.js.map

Lines changed: 14 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/JavaLibraryScript.min.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/JavaLibraryScript.min.js.map

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "javalibraryscript",
3-
"version": "v1.0.4.2",
3+
"version": "v1.0.4.3",
44
"description": "Javaの機能をJavaScriptに適当に再現",
55
"main": "index.js",
66
"scripts": {

src/base/Interface.js

Lines changed: 68 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@ const TypeChecker = require("../libs/TypeChecker.js");
66
* @class
77
*/
88
class Interface extends JavaLibraryScriptCore {
9-
static _isDebugMode = false;
9+
static _isDebugMode = true;
1010

1111
/**
12-
* 型定義とメゾットの強制実装
12+
* 型定義
1313
* @param {Function} TargetClass - 型定義を追加するクラス
1414
* @param {{[String]: {"args": Function[], "returns": Function[]}}} [newMethods] - 追加するメソッド群
1515
* @param {Object} [opt] - オプション
@@ -18,8 +18,6 @@ class Interface extends JavaLibraryScriptCore {
1818
* @static
1919
*/
2020
static applyTo(TargetClass, newDefs = {}, { inherit = true } = {}) {
21-
if (!this._isDebugMode) return;
22-
2321
const proto = TargetClass.prototype;
2422

2523
// 継承モードなら親の型定義をマージ
@@ -43,46 +41,80 @@ class Interface extends JavaLibraryScriptCore {
4341

4442
// 継承+新規定義マージ(子定義優先)
4543
Object.assign(proto.__interfaceTypes, inheritedDefs, newDefs);
44+
}
4645

47-
for (const methodName in proto.__interfaceTypes) {
48-
const def = proto.__interfaceTypes[methodName];
49-
const original = proto[methodName];
50-
51-
if (typeof original !== "function") {
52-
throw new Error(`"${TargetClass.name}" はメソッド "${methodName}" を実装する必要があります`);
53-
}
46+
/**
47+
* 型定義とメゾットの強制実装
48+
* @param {Function} TargetClass - 型定義を追加するクラス
49+
* @param {{[String]: {"args": Function[], "returns": Function[]}}} [newMethods] - 追加するメソッド群
50+
* @param {Object} [opt] - オプション
51+
* @param {boolean} [opt.inherit=true] - 継承モード
52+
* @param {boolean} [opt.abstract=true] - 抽象クラス化
53+
* @returns {Function}
54+
* @static
55+
*/
56+
static convert(TargetClass, newDefs = {}, { inherit = true, abstract = true } = {}) {
57+
this.applyTo(TargetClass, newDefs, { inherit });
5458

55-
proto[methodName] = function (...args) {
56-
// 引数チェック
57-
const expectedArgs = def.args || [];
58-
for (let i = 0; i < expectedArgs.length; i++) {
59-
if (!TypeChecker.matchType(args[i], expectedArgs[i])) {
60-
throw new TypeError(`"${TargetClass.name}.${methodName}" 第${i + 1}引数: ${TypeChecker.typeNames(expectedArgs[i])} を期待 → 実際: ${TypeChecker.stringify(args[i])}`);
59+
const interfaceClass = class extends TargetClass {
60+
constructor(...args) {
61+
if (abstract) {
62+
if (new.target === interfaceClass) {
63+
throw new TypeError(`Cannot instantiate abstract class ${TargetClass.name}`);
6164
}
6265
}
66+
super(...args);
67+
68+
if (!Interface._isDebugMode) return;
69+
70+
const proto = Object.getPrototypeOf(this);
71+
const defs = proto.__interfaceTypes || {};
6372

64-
const result = original.apply(this, args);
65-
const ret = def.returns;
66-
const expectedReturn = TypeChecker.checkFunction(ret) ? ret(args) : ret;
67-
68-
const validate = (val) => {
69-
if (!TypeChecker.matchType(val, expectedReturn)) {
70-
if (expectedReturn === InterfaceTypeChecker.NO_RETURN) {
71-
throw new TypeError(`"${TargetClass.name}.${methodName}" は戻り値を返してはいけません → 実際: ${TypeChecker.stringify(val)}`);
72-
} else {
73-
throw new TypeError(`"${TargetClass.name}.${methodName}" の戻り値: ${TypeChecker.typeNames(expectedReturn)} を期待 → 実際: ${TypeChecker.stringify(val)}`);
74-
}
73+
for (const methodName of Object.keys(defs)) {
74+
const def = defs[methodName];
75+
const original = this[methodName];
76+
77+
if (typeof original !== "function") {
78+
throw new Error(`"${this.constructor.name}" はメソッド "${methodName}" を実装する必要があります`);
7579
}
76-
return val;
77-
};
7880

79-
if (result instanceof Promise) {
80-
return result.then(validate);
81-
} else {
82-
return validate(result);
81+
// ラップは一度だけ(重複防止)
82+
if (!original.__isWrapped) {
83+
const wrapped = (...args) => {
84+
// 引数チェック
85+
const expectedArgs = def.args || [];
86+
for (let i = 0; i < expectedArgs.length; i++) {
87+
if (!TypeChecker.matchType(args[i], expectedArgs[i])) {
88+
throw new TypeError(`"${this.constructor.name}.${methodName}" 第${i + 1}引数: ${TypeChecker.typeNames(expectedArgs[i])} を期待 → 実際: ${TypeChecker.stringify(args[i])}`);
89+
}
90+
}
91+
92+
const result = original.apply(this, args);
93+
const expectedReturn = TypeChecker.checkFunction(def.returns) ? def.returns(args) : def.returns;
94+
95+
const validate = (val) => {
96+
if (!TypeChecker.matchType(val, expectedReturn)) {
97+
if (expectedReturn === TypeChecker.NoReturn) {
98+
throw new TypeError(`"${this.constructor.name}.${methodName}" は戻り値を返してはいけません → 実際: ${TypeChecker.stringify(val)}`);
99+
} else {
100+
throw new TypeError(`"${this.constructor.name}.${methodName}" の戻り値: ${TypeChecker.typeNames(expectedReturn)} を期待 → 実際: ${TypeChecker.stringify(val)}`);
101+
}
102+
}
103+
return val;
104+
};
105+
106+
return result instanceof Promise ? result.then(validate) : validate(result);
107+
};
108+
wrapped.__isWrapped = true;
109+
this[methodName] = wrapped;
110+
}
83111
}
84-
};
85-
}
112+
}
113+
};
114+
115+
Object.defineProperty(interfaceClass, "name", { value: TargetClass.name });
116+
117+
return interfaceClass;
86118
}
87119
}
88120

src/util/HashMap.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
const BaseMap = require("./BaseMap");
1+
const MapInterface = require("./MapInterface");
22
const Interface = require("../base/Interface");
33
const EntryStream = require("./stream/EntryStream.js");
44

55
/**
66
* 型チェック機能のついたMap
77
* @class
88
*/
9-
class HashMap extends BaseMap {
9+
class HashMap extends MapInterface {
1010
/**
1111
* @param {Function} KeyType
1212
* @param {Function} ValueType
@@ -209,6 +209,4 @@ class HashMap extends BaseMap {
209209
}
210210
}
211211

212-
Interface.applyTo(HashMap);
213-
214212
module.exports = HashMap;
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,16 @@ const NotEmpty = [NotNull, NotUndefined];
1111
/**
1212
* Mapの基底クラス
1313
* @class
14+
* @abstract
15+
* @interface
1416
*/
15-
class BaseMap extends Map {
17+
class MapInterface extends Map {
1618
/**
1719
* @param {Function} KeyType
1820
* @param {Function} ValueType
1921
*/
2022
constructor(KeyType, ValueType) {
2123
super();
22-
if (new.target === BaseMap) {
23-
throw new TypeError("Cannot instantiate abstract class BaseMap");
24-
}
25-
2624
this._KeyType = KeyType;
2725
this._ValueType = ValueType;
2826
}
@@ -50,7 +48,7 @@ class BaseMap extends Map {
5048
}
5149
}
5250

53-
Interface.applyTo(BaseMap, {
51+
module.exports = Interface.convert(MapInterface, {
5452
set: { args: [NotEmpty, NotEmpty], returns: Any },
5553
put: { args: [NotEmpty, NotEmpty], returns: Any },
5654
get: { args: [NotEmpty], returns: Any },
@@ -62,5 +60,3 @@ Interface.applyTo(BaseMap, {
6260
containsKey: { args: [NotEmpty], returns: Boolean },
6361
containsValue: { args: [NotEmpty], returns: Boolean },
6462
});
65-
66-
module.exports = BaseMap;

src/util/SetInterface.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const Interface = require("../base/Interface");
2+
const TypeChecker = require("../libs/TypeChecker");
3+
4+
const Any = TypeChecker.Any;
5+
const NoReturn = TypeChecker.NoReturn;
6+
const NotNull = TypeChecker.NotNull;
7+
const NotUndefined = TypeChecker.NotUndefined;
8+
9+
const NotEmpty = [NotNull, NotUndefined];
10+
11+
/**
12+
* Mapの基底クラス
13+
* @class
14+
* @abstract
15+
* @interface
16+
*/
17+
class SetInterface extends Set {
18+
/**
19+
* @param {Function} ValueType
20+
*/
21+
constructor(ValueType) {
22+
super();
23+
this._ValueType = ValueType;
24+
}
25+
26+
/**
27+
* Keyの型をチェックする
28+
* @param {any} key
29+
* @throws {TypeError}
30+
*/
31+
_checkKey(key) {
32+
if (!TypeChecker.matchType(key, this._KeyType)) {
33+
throw new TypeError(`キー型が一致しません。期待: ${TypeChecker.typeNames(this._KeyType)} → 実際: ${TypeChecker.stringify(key)}`);
34+
}
35+
}
36+
37+
/**
38+
* Valueの型をチェックする
39+
* @param {any} value
40+
* @throws {TypeError}
41+
*/
42+
_checkValue(value) {
43+
if (!TypeChecker.matchType(value, this._ValueType)) {
44+
throw new TypeError(`値型が一致しません。期待: ${TypeChecker.typeNames(this._ValueType)} → 実際: ${TypeChecker.stringify(value)}`);
45+
}
46+
}
47+
}
48+
49+
module.exports = Interface.convert(SetInterface, {
50+
set: { args: [NotEmpty, NotEmpty], returns: Any },
51+
put: { args: [NotEmpty, NotEmpty], returns: Any },
52+
get: { args: [NotEmpty], returns: Any },
53+
delete: { args: [NotEmpty], returns: Boolean },
54+
remove: { args: [NotEmpty], returns: Boolean },
55+
isEmpty: { returns: Boolean },
56+
clear: { returns: NoReturn },
57+
has: { args: [NotEmpty], returns: Boolean },
58+
containsKey: { args: [NotEmpty], returns: Boolean },
59+
containsValue: { args: [NotEmpty], returns: Boolean },
60+
});

src/util/index.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module.exports = {
2-
BaseMap: require("./BaseMap.js"),
32
HashMap: require("./HashMap.js"),
3+
MapInterface: require("./MapInterface.js"),
4+
SetInterface: require("./SetInterface.js"),
45
stream: require("./stream/index.js")
56
};

0 commit comments

Comments
 (0)