1/**
2 * Copyright (c) 2022 Huawei Device Co., Ltd.
3 * Licensed under the Apache License, Version 2.0 (the "License");
4 * you may not use this file except in compliance with the License.
5 * You may obtain a copy of the License at
6 *
7 *     http://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15
16/**
17 * @file: Deep copy
18 */
19import Method from './Method';
20
21const mapType = '[object Map]';
22const setType = '[object Set]';
23const arrayType = '[object Array]';
24const objectType = '[object Object]';
25const argsType = '[object Arguments]';
26const boolType = '[object Boolean]';
27const dateType = '[object Date]';
28const numberType = '[object Number]';
29const stringType = '[object String]';
30const symbolType = '[object Symbol]';
31const errorType = '[object Error]';
32const regexpType = '[object RegExp]';
33const funcType = '[object Function]';
34const deepType = [mapType, setType, arrayType, objectType, argsType];
35
36export default class Getclone {
37  private static getclone: Getclone = new Getclone();
38
39  public static getInstance(): Getclone {
40    return this.getclone;
41  }
42
43  /**
44   * forEach
45   *
46   * @param { array } array
47   *
48   * @param { function } iteratee
49   */
50  private forEach(array, iteratee) {
51    let index = -1;
52    const length = array.length;
53
54    while (++index < length) {
55      iteratee(array[index], index);
56    }
57    return array;
58  }
59
60  /**
61   * is object
62   *
63   * @param { any } target
64   */
65  private isObject(target) {
66    const type = typeof target;
67    return target !== null && (type === 'object' || type === 'function');
68  }
69
70  /**
71   * get type
72   *
73   * @param { any } target
74   */
75  private getType(target) {
76    return Object.prototype.toString.call(target);
77  }
78
79  /**
80   * get init
81   *
82   * @param { any } target
83   */
84  private getInit(target) {
85    const ctor = target.constructor;
86    return new ctor();
87  }
88
89  /**
90   * clone symbol
91   *
92   * @param { any } target
93   */
94  private cloneSymbo(target) {
95    return Object(Symbol.prototype.valueOf.call(target));
96  }
97
98  /**
99   * clone reg
100   *
101   * @param { any } target
102   */
103  private cloneRegister(target) {
104    const reFlags = /\w*$/;
105    const result = new target.constructor(target.source, reFlags.exec(target));
106    result.lastIndex = target.lastIndex;
107    return result;
108  }
109
110  /**
111   * clone function
112   *
113   * @param { function } func
114   */
115  private cloneFunc(func) {
116    const bodyRegister = /(?<={)(.|\n)+(?=})/m;
117    const paramRegister = /(?<=\().+(?=\)\s+{)/;
118    const funcStr = func.toString();
119    if (func.prototype) {
120      const param = paramRegister.exec(funcStr);
121      const bodys = bodyRegister.exec(funcStr);
122      if (bodys) {
123        if (param) {
124          const paramArray = param[0].split(',');
125          return new Function(...paramArray, bodys[0]);
126        } else {
127          return new Function(bodys[0]);
128        }
129      } else {
130        return null;
131      }
132    } else {
133      return eval(funcStr);
134    }
135  }
136
137  /**
138   * clone other type
139   *
140   * @param { any } target
141   *
142   * @param { any } type - data type
143   */
144  private cloneOtherType(target, type) {
145    const ctor = target.constructor;
146    switch (type) {
147      case boolType:
148      case numberType:
149      case stringType:
150      case errorType:
151      case dateType:
152        return new ctor(target);
153      case regexpType:
154        return this.cloneRegister(target);
155      case symbolType:
156        return this.cloneSymbo(target);
157      case funcType:
158        return this.cloneFunc(target);
159      default:
160        return null;
161    }
162  }
163
164  /**
165   * clone
166   *
167   * @param { any } target
168   *
169   * @param { map } map
170   */
171  public clone(target, map = new WeakMap()) {
172    if (!this.isObject(target)) {
173      return target;
174    }
175
176    const type = this.getType(target);
177    let cloneTargets;
178
179    if (Method.includes(deepType, type)) {
180      cloneTargets = this.getInit(target);
181    } else {
182      return this.cloneOtherType(target, type);
183    }
184
185    if (map.get(target)) {
186      return map.get(target);
187    }
188    map.set(target, cloneTargets);
189
190    if (type === setType) {
191      target.forEach((value) => {
192        cloneTargets.add(this.clone(value, map));
193      });
194      return cloneTargets;
195    }
196
197    if (type === mapType) {
198      target.forEach((value, key) => {
199        cloneTargets.set(key, this.clone(value, map));
200      });
201      return cloneTargets;
202    }
203
204    const keys = type === arrayType ? undefined : Object.keys(target);
205    this.forEach(keys || target, (value, key) => {
206      if (keys) {
207        key = value;
208      }
209      cloneTargets[key] = this.clone(target[key], map);
210    });
211
212    return cloneTargets;
213  }
214}