This commit is contained in:
Looly 2023-06-09 00:50:16 +08:00
parent ca2594a08c
commit 905ddbd8dd
11 changed files with 311 additions and 814 deletions

View File

@ -0,0 +1,302 @@
/*
* Copyright (c) 2023 looly(loolly@aliyun.com)
* Hutool is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package org.dromara.hutool.core.data;
import org.dromara.hutool.core.lang.Assert;
import org.dromara.hutool.core.map.MapUtil;
import org.dromara.hutool.core.regex.PatternPool;
import org.dromara.hutool.core.regex.ReUtil;
import java.time.Year;
import java.util.HashMap;
import java.util.Map;
/**
* VIN是Vehicle Identification Number的缩写即车辆识别号码VIN码是全球通行的车辆唯一标识符由17位数字和字母组成
* <p>
* 不同位数代表着不同意义具体解释如下
* <ul>
* <li>1-3位WMI制造商标示符代表车辆制造商信息</li>
* <li>4-8位VDS车型识别代码代表车辆品牌车系车型及其排量等信息</li>
* <li>9位校验位通过公式计算出来用于验证VIN码的正确性</li>
* <li>10位年份代号代表车辆生产的年份</li>
* <li>11位工厂代码代表车辆生产工厂信息</li>
* <li>12-17位流水号代表车辆的生产顺序号</li>
* </ul>
* VIN码可以找到汽车详细的个人工程制造方面的信息是判定一个汽车合法性及其历史的重要依据
* <p>
* 本实现参考以下标准
* <ul>
* <li><a href="https://www.iso.org/standard/52200.html">ISO 3779</a></li>
* <li><a href="http://www.catarc.org.cn/upload/202004/24/202004241005284241.pdf">车辆识别代号管理办法</a></li>
* <li><a href="https://en.wikipedia.org/wiki/Vehicle_identification_number">Wikipedia</a></li>
* <li><a href="https://openstd.samr.gov.cn/bzgk/gb/newGbInfo?hcno=E2EBF667F8C032B1EDFD6DF9C1114E02">GB 16735-2019</a></li>
* </ul>
*
* @author dax
* @since 6.0.0
*/
public class VIN {
/**
* 加权系数见附录A表A.3
*/
private static final int[] WEIGHT = {8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2};
private static final int YEAR_LOOP = 30;
private static final char[] YEAR_ID = {
'A', 'B', 'C', 'D', 'E',
'F', 'G', 'H', 'J', 'K',
'L', 'M', 'N', 'P', 'R',
'S', 'T', 'V', 'W', 'X',
'Y', '1', '2', '3', '4',
'5', '6', '7', '8', '9'
};
private static final Map<Character, Integer> YEAR_MAP;
static {
final Map<Character, Integer> yearMap = new HashMap<>(YEAR_ID.length, 1);
for (int i = 0; i < YEAR_ID.length; i++) {
yearMap.put(YEAR_ID[i], i);
}
YEAR_MAP = MapUtil.view(yearMap);
}
/**
* 创建VIN
*
* @param vinCode VIN码
* @return VIN对象
*/
public static VIN of(final String vinCode) {
return new VIN(vinCode);
}
private final String code;
/**
* 构造
*
* @param vinCode VIN码
*/
public VIN(final String vinCode) {
Assert.isTrue(verify(vinCode), "Invalid VIN code!");
this.code = vinCode;
}
/**
* 获取VIN码
*
* @return VIN码
*/
public String getCode(){
return this.code;
}
/**
* 获取国家或地区代码
*
* @return 国家或地区代码
*/
public String getCountryCode() {
return this.code.substring(0, 2);
}
/**
* 获取世界制造厂识别代号WMIWorld Manufacturer Identifier<br>
* 对年产量大于或等于1000辆的完整车辆或非完整车辆制造,车辆识别代号的第一部分为世界制造)厂识别代号(WMI)<br>
* 对年产量小于1000辆的完整车辆和/或非完整车辆制造厂第三部分的三五位与第一部分的三位字码一起构成世界制造厂识别代号(WMI)
*
* @return WMI
*/
public String getWMI() {
final String wmi = this.code.substring(0, 3);
return isLessThan1000() ? wmi + this.code.substring(11, 14) : wmi;
}
/**
* 是否是年产量小于1000的车辆制造厂
*
* @return 是否年产量小于1000
*/
public boolean isLessThan1000() {
return '9' == this.code.charAt(2);
}
/**
* 获取车辆说明部分 VDSVehicle Descriptor section
*
* @return VDS值
*/
public String getVDS() {
return this.code.substring(3, 9);
}
/**
* 获取车辆特征代码Vehicle Descriptor Code相对于VDS不包含校验位
*
* @return 车辆特征代码
*/
public String getVehicleDescriptorCode() {
return this.code.substring(3, 8);
}
/**
* 获取车辆指示部分 VISVehicle Indicator Section
*
* @return VIS
*/
public String getVIS() {
return this.code.substring(9);
}
/**
* Get year.
*
* @return the year
*/
public Year getYear() {
return getYear(1);
}
/**
* 获取装配厂字码
*
* @return 由厂家自行定义的装配厂字码 string
*/
public char getOemCode() {
return this.code.charAt(10);
}
/**
* Gets year.
*
* @param multiple 1 代表从 1980年开始的第一个30年
* @return the year
*/
public Year getYear(final int multiple) {
final int year = 1980 + YEAR_LOOP * multiple + YEAR_MAP.get(this.code.charAt(9)) % YEAR_LOOP;
return Year.of(year);
}
/**
* 生产序号
* <p>
* 年产量大于1000为6位年产量小于1000的为3位
*
* @return 生产序号 string
*/
public String getProdNo() {
return this.code.substring(isLessThan1000() ? 14 : 11, 17);
}
/**
* 校验VIN码有效性要求
* <ul>
* <li>满足正则{@link PatternPool#CAR_VIN}</li>
* <li>满足第9位校验位和计算的检验值一致</li>
* </ul>
*
* @param vinCode VIN码
* @return 是否有效
*/
public static boolean verify(final String vinCode) {
Assert.notBlank(vinCode, "VIN code must be not blank!");
if (!ReUtil.isMatch(PatternPool.CAR_VIN, vinCode)) {
return false;
}
return vinCode.charAt(8) == calculateVerifyCode(vinCode);
}
/**
* 计算校验值见附录A
*
* @param vinCode VIN码
* @return 校验值
*/
private static char calculateVerifyCode(final String vinCode) {
int sum = 0;
for (int i = 0; i < 17; i++) {
sum += getWeightFactor(vinCode, i);
}
final int factor = sum % 11;
return factor != 10 ? (char) (factor + '0') : 'X';
}
/**
* 获取对应位置字符的权重因子
*
* @param vinCode VIN码
* @param i 位置
* @return 权重因子
*/
private static int getWeightFactor(final String vinCode, final int i) {
final char c = vinCode.charAt(i);
return getVinValue(c) * WEIGHT[i];
}
/**
* 获取字母对应值附录A表A.2
*
* @param vinCodeChar VIN编码中的字符
* @return 对应值
*/
private static int getVinValue(final char vinCodeChar) {
switch (vinCodeChar) {
case '0':
return 0;
case '1':
case 'J':
case 'A':
return 1;
case '2':
case 'S':
case 'K':
case 'B':
return 2;
case '3':
case 'T':
case 'L':
case 'C':
return 3;
case '4':
case 'U':
case 'M':
case 'D':
return 4;
case '5':
case 'V':
case 'N':
case 'E':
return 5;
case '6':
case 'W':
case 'F':
return 6;
case '7':
case 'P':
case 'X':
case 'G':
return 7;
case '8':
case 'Y':
case 'H':
return 8;
case '9':
case 'Z':
case 'R':
return 9;
}
throw new IllegalArgumentException("Invalid VIN char: " + vinCodeChar);
}
}

View File

@ -1,53 +0,0 @@
package org.dromara.hutool.core.data.vin;
/**
* 可以是字母或者数字的字码
*
* @author dax
* @since 2023 /5/14 17:56
*/
class AlphanumericVinCode implements MaskVinCode {
private final String code;
private final int index;
private final int mask;
/**
* 该构造会校验字码是否符合GB16735标准
*
* @param code 字码值
* @param index 索引位
*/
AlphanumericVinCode(String code, int index) {
this.code = code;
this.index = index;
int weight = WEIGHT_FACTORS.get(index);
mask = NUMERIC.matcher(this.code).matches() ?
Integer.parseInt(this.code) * weight :
VinCodeMaskEnum.valueOf(this.code).getMaskCode() * weight;
}
@Override
public String getCode() {
return code;
}
@Override
public int getMask() {
return mask;
}
@Override
public int getIndex() {
return index;
}
@Override
public String toString() {
return "AlphanumericVinCode{" +
"code='" + code + '\'' +
", index=" + index +
", mask=" + mask +
'}';
}
}

View File

@ -1,36 +0,0 @@
package org.dromara.hutool.core.data.vin;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
/**
* 支持掩码的字码
*
* @author dax
* @since 2023 /5/15 10:43
*/
interface MaskVinCode extends VinCode {
/**
* 字码权重因子
*/
List<Integer> WEIGHT_FACTORS = Arrays.asList(8, 7, 6, 5, 4, 3, 2, 10, 0, 9, 8, 7, 6, 5, 4, 3, 2);
/**
* 数字位校验
*/
Pattern NUMERIC = Pattern.compile("^\\d$");
/**
* 获取掩码字码编码*加权值
*
* @return the mask
*/
int getMask();
/**
* 所在位置索引,[0,16]
*
* @return the index
*/
int getIndex();
}

View File

@ -1,55 +0,0 @@
package org.dromara.hutool.core.data.vin;
/**
* 数字字码.
*
* @author dax
* @since 2023 /5/14 17:42
*/
class NumericVinCode implements MaskVinCode {
private final String code;
private final int index;
private final int mask;
/**
* 该构造会校验字码是否符合GB16735标准
*
* @param code 字码值
* @param index 索引位
* @throws IllegalArgumentException 校验
*/
NumericVinCode(String code, int index) throws IllegalArgumentException {
if (!NUMERIC.matcher(code).matches()) {
throw new IllegalArgumentException("索引为 " + index + " 的字码必须是数字");
}
this.code = code;
this.index = index;
this.mask = Integer.parseInt(code) * WEIGHT_FACTORS.get(index);
}
@Override
public String getCode() {
return code;
}
@Override
public int getMask() {
return mask;
}
@Override
public int getIndex() {
return index;
}
@Override
public String toString() {
return "NumericCode{" +
"code='" + code + '\'' +
", index=" + index +
", mask=" + mask +
'}';
}
}

View File

@ -1,87 +0,0 @@
package org.dromara.hutool.core.data.vin;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* VDS
*
* @author dax
* @since 2023 /5/15 9:37
*/
class Vds implements VinCode {
private final List<AlphanumericVinCode> vdCode;
private final AlphanumericVinCode checksum;
private final String code;
private final int mask;
/**
* Instantiates a new Vds.
*
* @param vdCode the vd code
*/
Vds(List<AlphanumericVinCode> vdCode) {
this.vdCode = vdCode.subList(0, 5);
this.checksum = vdCode.get(5);
this.code = vdCode.stream().map(AlphanumericVinCode::getCode).collect(Collectors.joining());
this.mask = vdCode.stream().mapToInt(AlphanumericVinCode::getMask).sum();
}
/**
* 从VIN生成VDS
*
* @param vin the vin
* @return the vds
*/
public static Vds from(String vin) {
List<AlphanumericVinCode> vdCode = IntStream.range(3, 9)
.mapToObj(index ->
new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index))
.collect(Collectors.toList());
return new Vds(vdCode);
}
/**
* Gets vd code.
*
* @return the vd code
*/
List<AlphanumericVinCode> getVdCode() {
return vdCode;
}
/**
* Gets checksum.
*
* @return the checksum
*/
AlphanumericVinCode getChecksum() {
return checksum;
}
@Override
public String getCode() {
return code;
}
/**
* Gets mask.
*
* @return the mask
*/
int getMask() {
return mask;
}
@Override
public String toString() {
return "Vds{" +
"vdCode=" + vdCode +
", checksum=" + checksum +
", code='" + code + '\'' +
", mask=" + mask +
'}';
}
}

View File

@ -1,211 +0,0 @@
package org.dromara.hutool.core.data.vin;
import java.time.Year;
import java.util.Objects;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
/**
* VIN是Vehicle Identification Number的缩写即车辆识别号码VIN码是全球通行的车辆唯一标识符由17位数字和字母组成
* <p>
* 不同位数代表着不同意义具体解释如下
* <ul>
* <li>1-3位制造商标示符代表车辆制造商信息</li>
* <li>4-8位车型识别代码代表车辆品牌车系车型及其排量等信息</li>
* <li>9位校验位通过公式计算出来用于验证VIN码的正确性</li>
* <li>10位年份代号代表车辆生产的年份</li>
* <li>11位工厂代码代表车辆生产工厂信息</li>
* <li>12-17位流水号代表车辆的生产顺序号</li>
* </ul>
* VIN码可以找到汽车详细的个人工程制造方面的信息是判定一个汽车合法性及其历史的重要依据
* <p>
* 本实现参考以下标准
* <ul>
* <li><a href="https://www.iso.org/standard/52200.html">ISO 3779</a></li>
* <li><a href="http://www.catarc.org.cn/upload/202004/24/202004241005284241.pdf">车辆识别代号管理办法</a></li>
* <li><a href="https://en.wikipedia.org/wiki/Vehicle_identification_number">Wikipedia</a></li>
* </ul>
*
* @author dax
* @since 2023 /5/15 9:40
*/
public final class Vin implements VinCode {
private static final Pattern GB16735_VIN_REGEX = Pattern.compile("^[A-HJ-NPR-Z\\d]{8}[X\\d][A-HJ-NPR-Z\\d]{8}$");
private final Wmi wmi;
private final Vds vds;
private final Vis vis;
private final String code;
/**
* Instantiates a new Vin.
*
* @param wmi the wmi
* @param vds the vds
* @param vis the vis
* @param code the code
*/
Vin(Wmi wmi, Vds vds, Vis vis, String code) {
this.wmi = wmi;
this.vds = vds;
this.vis = vis;
this.code = code;
}
/**
* 从VIN字符串生成{@code Vin}对象
*
* @param vin VIN字符串
* @return VIN对象 vin
*/
public static Vin of(String vin) throws IllegalArgumentException {
if (!GB16735_VIN_REGEX.matcher(vin).matches()) {
throw new IllegalArgumentException("VIN格式不正确需满足正则 " + GB16735_VIN_REGEX.pattern());
}
Wmi wmi = Wmi.from(vin);
Vds vds = Vds.from(vin);
Vis vis = Vis.from(vin);
int factor = (wmi.getMask() + vds.getMask() + vis.getMask()) % 11;
String checked = factor != 10 ? String.valueOf(factor) : "X";
if (!Objects.equals(vds.getChecksum().getCode(), checked)) {
throw new IllegalArgumentException("VIN校验不通过");
}
return new Vin(wmi, vds, vis, vin);
}
/**
* 仅判断一个字符串是否符合VIN规则
*
* @param vinStr vinStr
* @return {@code true} 符合
*/
public static boolean isValidVinCode(String vinStr) {
if (GB16735_VIN_REGEX.matcher(vinStr).matches()) {
int weights = IntStream.range(0, 17)
.map(i -> calculateWeight(vinStr, i))
.sum();
int factor = weights % 11;
char checked = factor != 10 ? (char) (factor + '0') : 'X';
return vinStr.charAt(8) == checked;
}
return false;
}
private static int calculateWeight(String vinStr, int i) {
char c = vinStr.charAt(i);
Integer factor = MaskVinCode.WEIGHT_FACTORS.get(i);
return c <= '9' ?
Character.getNumericValue(c) * factor :
VinCodeMaskEnum.valueOf(String.valueOf(c)).getMaskCode() * factor;
}
/**
* 标识一个国家或者地区
*
* @return the string
*/
public String geoCode() {
String wmiCode = this.wmi.getCode();
return wmiCode.substring(0, 2);
}
/**
* 制造厂标识码
* <p>
* 年产量大于1000为符合GB16737规定的{@link Wmi}年产量小于1000固定为9需要结合VIN的第121314位字码确定唯一
*
* @return 主机厂识别码 string
*/
public String manufacturerCode() {
String wmiCode = this.wmi.getCode();
return isLessThan1000() ?
wmiCode.concat(this.vis.getProdNoStr().substring(0, 3)) : wmiCode;
}
/**
* 是否是年产量小于1000的车辆制造厂
*
* @return 是否年产量小于1000 boolean
*/
public boolean isLessThan1000() {
return this.wmi.isLessThan1000();
}
/**
* 获取WMI码
*
* @return WMI值 string
*/
public String wmiCode() {
return wmi.getCode();
}
/**
* 获取车辆特征描述码
*
* @return VDS值 string
*/
public String vdsCode() {
return this.vds.getCode().substring(0, 5);
}
/**
* 获取默认车型年份接近于本年度
*
* @return the int
*/
public Year defaultYear() {
return this.year(1);
}
/**
* 获取车型年份
* <p>
* 自1980年起30年一个周期
*
* @param multiple 1 代表从 1980年开始的第一个30年
* @return 返回年份对象 year
* @see <a href="https://en.wikipedia.org/wiki/Vehicle_identification_number#Model_year_encoding">年份编码模型</a>
*/
public Year year(int multiple) {
return this.vis.getYear(multiple);
}
/**
* 生产序号
* <p>
* 年产量大于1000为6位年产量小于1000的为3位
*
* @return 生产序号 string
*/
public String prodNo() {
String prodNoStr = this.vis.getProdNoStr();
return isLessThan1000() ?
prodNoStr.substring(3, 6) : prodNoStr;
}
/**
* 获取装配厂字码
*
* @return 由厂家自行定义的装配厂字码 string
*/
public String oemCode() {
return this.vis.getOem().getCode();
}
@Override
public String getCode() {
return code;
}
@Override
public String toString() {
return "Vin{" +
"wmi=" + wmi +
", vds=" + vds +
", vis=" + vis +
", code='" + code + '\'' +
'}';
}
}

View File

@ -1,18 +0,0 @@
package org.dromara.hutool.core.data.vin;
/**
* 汽车Vin字码抽象.
*
* @author dax
* @since 2023 /5/14 17:42
*/
interface VinCode {
/**
* 获取字码值
*
* @return the code
*/
String getCode();
}

View File

@ -1,117 +0,0 @@
package org.dromara.hutool.core.data.vin;
/**
* Vin掩码枚举
*
* @author dax
* @since 2023 /5/15 10:49
*/
enum VinCodeMaskEnum {
/**
* A 掩码.
*/
A(1),
/**
* B 掩码.
*/
B(2),
/**
* C 掩码.
*/
C(3),
/**
* D 掩码.
*/
D(4),
/**
* E 掩码.
*/
E(5),
/**
* F 掩码.
*/
F(6),
/**
* G 掩码.
*/
G(7),
/**
* H 掩码.
*/
H(8),
/**
* J 掩码.
*/
J(1),
/**
* K 掩码.
*/
K(2),
/**
* L 掩码.
*/
L(3),
/**
* M 掩码.
*/
M(4),
/**
* N 掩码.
*/
N(5),
/**
* P 掩码.
*/
P(7),
/**
* R 掩码.
*/
R(9),
/**
* S 掩码.
*/
S(2),
/**
* T 掩码.
*/
T(3),
/**
* U 掩码.
*/
U(4),
/**
* V 掩码.
*/
V(5),
/**
* W 掩码.
*/
W(6),
/**
* X 掩码.
*/
X(7),
/**
* Y 掩码.
*/
Y(8),
/**
* Z 掩码.
*/
Z(9);
private final int maskCode;
VinCodeMaskEnum(int maskCode) {
this.maskCode = maskCode;
}
/**
* 获取掩码值.
*
* @return the mask code
*/
public int getMaskCode() {
return maskCode;
}
}

View File

@ -1,139 +0,0 @@
package org.dromara.hutool.core.data.vin;
import java.time.Year;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* VIS
*
* @author dax
* @since 2023 /5/15 12:07
*/
class Vis implements VinCode {
private static final int YEAR_LOOP = 30;
private static final List<String> YEAR_ID = Arrays.asList(
"A", "B", "C", "D", "E",
"F", "G", "H", "J", "K",
"L", "M", "N", "P", "R",
"S", "T", "V", "W", "X",
"Y", "1", "2", "3", "4",
"5", "6", "7", "8", "9");
private static final Map<String, Integer> YEAR_MAP = new HashMap<>();
static {
for (int i = 0; i < YEAR_ID.size(); i++) {
YEAR_MAP.put(YEAR_ID.get(i), i);
}
}
private final AlphanumericVinCode year;
private final AlphanumericVinCode oem;
private final List<MaskVinCode> prodNo;
private final String prodNoStr;
private final String code;
private final int mask;
/**
* Instantiates a new Vis.
*
* @param year the year
* @param oem the oem
* @param prodNo the prod no
*/
Vis(AlphanumericVinCode year, AlphanumericVinCode oem, List<MaskVinCode> prodNo) {
this.year = year;
this.oem = oem;
this.prodNo = prodNo;
this.prodNoStr = prodNo.stream().map(MaskVinCode::getCode).collect(Collectors.joining());
this.code = year.getCode() + oem.getCode() + prodNo.stream()
.map(MaskVinCode::getCode)
.collect(Collectors.joining());
this.mask = year.getMask() + oem.getMask() + prodNo.stream().mapToInt(MaskVinCode::getMask).sum();
}
/**
* 从VIN生成VIS
*
* @param vin the vin
* @return the vis
*/
static Vis from(String vin) {
AlphanumericVinCode year = new AlphanumericVinCode(String.valueOf(vin.charAt(9)), 9);
AlphanumericVinCode factory = new AlphanumericVinCode(String.valueOf(vin.charAt(10)), 10);
List<MaskVinCode> codes = IntStream.range(11, 17)
.mapToObj(index -> index < 14 ? new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index) :
new NumericVinCode(String.valueOf(vin.charAt(index)), index))
.collect(Collectors.toList());
return new Vis(year, factory, codes);
}
/**
* Gets year.
*
* @param multiple the multiple
* @return the year
*/
Year getYear(int multiple) {
int year = 1980 + YEAR_LOOP * multiple + YEAR_MAP.get(this.year.getCode()) % YEAR_LOOP;
return Year.of(year);
}
/**
* Gets oem.
*
* @return the oem
*/
AlphanumericVinCode getOem() {
return oem;
}
/**
* Gets prod no.
*
* @return the prod no
*/
List<MaskVinCode> getProdNo() {
return prodNo;
}
/**
* Gets prod no str.
*
* @return the prod no str
*/
String getProdNoStr() {
return prodNoStr;
}
/**
* Gets mask.
*
* @return the mask
*/
int getMask() {
return mask;
}
@Override
public String getCode() {
return code;
}
@Override
public String toString() {
return "Vis{" +
"year=" + year +
", oem=" + oem +
", prodNo=" + prodNo +
", code='" + code + '\'' +
", mask=" + mask +
'}';
}
}

View File

@ -1,83 +0,0 @@
package org.dromara.hutool.core.data.vin;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
/**
* The type Wmi.
*
* @author dax
* @since 2023 /5/15 9:25
*/
class Wmi implements VinCode {
private final String code;
private final int mask;
/**
* The Wmi codes.
*/
List<AlphanumericVinCode> wmiCodes;
/**
* Instantiates a new Wmi.
*
* @param wmiCodes the wmi codes
*/
Wmi(List<AlphanumericVinCode> wmiCodes) {
this.wmiCodes = wmiCodes;
AtomicInteger mask = new AtomicInteger();
this.code = wmiCodes.stream()
.peek(alphanumericCode -> mask.addAndGet(alphanumericCode.getMask()))
.map(AlphanumericVinCode::getCode)
.collect(Collectors.joining());
this.mask = mask.get();
}
/**
* 从VIN生成WMI
*
* @param vin the vin
* @return the wmi
*/
static Wmi from(String vin) {
List<AlphanumericVinCode> codes = IntStream.range(0, 3)
.mapToObj(index ->
new AlphanumericVinCode(String.valueOf(vin.charAt(index)), index))
.collect(Collectors.toList());
return new Wmi(codes);
}
@Override
public String getCode() {
return code;
}
@Override
public String toString() {
return "Wmi{" +
"wmiCodes=" + wmiCodes +
", code='" + code + '\'' +
", mask=" + mask +
'}';
}
/**
* Gets mask.
*
* @return the mask
*/
int getMask() {
return mask;
}
/**
* 是否是年产量小于1000的车辆制造厂
*
* @return the boolean
*/
boolean isLessThan1000() {
return this.code.matches("^.*9$");
}
}

View File

@ -1,6 +1,5 @@
package org.dromara.hutool.core.data;
import org.dromara.hutool.core.data.vin.Vin;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
@ -8,34 +7,29 @@ import java.time.Year;
/**
* @author VampireAchao
* @since 2023/5/31 14:43
*/
public class VinTest {
@Test
public void parseVinTest() {
String vinStr = "HE9XR1C48PS083871";
Vin vin = Vin.of(vinStr);
final String vinStr = "HE9XR1C48PS083871";
final VIN vin = VIN.of(vinStr);
// VIN
Assertions.assertEquals("HE9XR1C48PS083871", vin.getCode());
// 是否合法
Assertions.assertTrue(Vin.isValidVinCode(vinStr));
// 年产量<1000
Assertions.assertTrue(vin.isLessThan1000());
// WMI
Assertions.assertEquals("HE9", vin.wmiCode());
// 地理区域码
Assertions.assertEquals("HE", vin.geoCode());
// 主机厂代码
Assertions.assertEquals("HE9083", vin.manufacturerCode());
Assertions.assertEquals("HE", vin.getCountryCode());
// WMI主机厂代码
Assertions.assertEquals("HE9083", vin.getWMI());
// VDS
Assertions.assertEquals("XR1C4", vin.vdsCode());
Assertions.assertEquals("XR1C48", vin.getVDS());
// 车型年份
Assertions.assertEquals(Year.of(2023), vin.defaultYear());
Assertions.assertEquals(Year.of(2023), vin.getYear());
// OEM厂商
Assertions.assertEquals("S", vin.oemCode());
Assertions.assertEquals('S', vin.getOemCode());
// 生产序号
Assertions.assertEquals("871", vin.prodNo());
Assertions.assertEquals("871", vin.getProdNo());
}
}