io.github.apkcloud.devicedetector.parser.AbstractParser Maven / Gradle / Ivy
Show all versions of DeviceDetector Show documentation
package io.github.apkcloud.devicedetector.parser;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import io.github.apkcloud.devicedetector.ClientHints;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 解析器的抽象类
*/
public abstract class AbstractParser {
/**
* 待解析的 UserAgent
*/
protected String userAgent;
/**
* 一般为已解析的 ClientHints
*/
protected ClientHints clientHints = null;
/**
* 正则表达式列表中所有项的串联正则表达式
*/
protected String overAllMatch;
/**
* 指示如何检测版本的深度
* 如果 maxMinorParts 为 0,仅返回主要版本
*/
protected static int maxMinorParts = 1;
/**
* 版本控制常量,用于设置版本控制最大为主要版本
* 版本示例:3, 5, 6, 200, 123, ...
*/
public static final int VERSION_TRUNCATION_MAJOR = 0;
/**
* 版本控制常量,用于设置版本控制最大为次要版本
* 版本示例:3.4, 5.6, 6.234, 0.200, 1.23, ...
*/
public static final int VERSION_TRUNCATION_MINOR = 1;
/**
* 版本控制常量,用于设置版本控制最大为路径级别
* 版本示例:3.4.0, 5.6.344, 6.234.2, 0.200.3, 1.2.3, ...
*/
public static final int VERSION_TRUNCATION_PATCH = 2;
/**
* 版本控制常量,用于设置版本控制为构建版本号
* 版本示例:3.4.0.12, 5.6.334.0, 6.234.2.3, 0.200.3.1, 1.2.3.0, ...
*/
public static final int VERSION_TRUNCATION_BUILD = 3;
/**
* 版本控制常量,用于设置版本控制为无限制(没有截断)
*/
public static final int VERSION_TRUNCATION_NONE = -1;
/**
* 返回解析器的内部名称
*
* @return String
*/
public abstract String getName();
public AbstractParser() {
userAgent = "";
}
public AbstractParser(String ua) {
userAgent = ua;
}
public AbstractParser(String ua, ClientHints clientHints) {
userAgent = ua;
this.clientHints = clientHints;
}
/**
* 设置 DeviceDetector 应如何返回版本
*
* @param type 任何一个 VERSION_TRUNCATION_* 常量
*/
public static void setVersionTruncation(int type) {
List list = Arrays.asList(VERSION_TRUNCATION_BUILD, VERSION_TRUNCATION_NONE, VERSION_TRUNCATION_MAJOR, VERSION_TRUNCATION_MINOR, VERSION_TRUNCATION_PATCH);
if (!list.contains(type)) {
return;
}
maxMinorParts = type;
}
/**
* 设置要解析的 UserAgent
*
* @param ua UserAgent
*/
public void setUserAgent(String ua) {
userAgent = ua;
}
/**
* 设置 ClientHints
*
* @param clientHints ClientHints
*/
public void setClientHints(ClientHints clientHints) {
this.clientHints = clientHints;
}
/**
* 返回解析yml文件的结果,yml文件路径定义在fixtureFile
*
* @return {@code ArrayList}
*/
protected T getRegexes(String fixtureFile, Class elementClass) {
T regexList;
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fixtureFile);
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
try {
regexList = objectMapper.readValue(inputStream, objectMapper.getTypeFactory().constructCollectionType(ArrayList.class, elementClass));
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
throw new RuntimeException("Could not load " + fixtureFile, e);
}
return regexList;
}
/**
* 返回解析yml文件的结果,yml文件路径定义在fixtureFile
*
* @return {@code LinkedHashMap}
*/
protected T getRegexes(String fixtureFile, Class keyClass, Class valueClass) {
T regexList;
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(fixtureFile);
ObjectMapper objectMapper = new ObjectMapper(new YAMLFactory());
try {
regexList = objectMapper.readValue(inputStream, objectMapper.getTypeFactory().constructMapType(LinkedHashMap.class, keyClass, valueClass));
if (inputStream != null) {
inputStream.close();
}
} catch (IOException e) {
throw new RuntimeException("Could not load " + fixtureFile, e);
}
return regexList;
}
/**
* 在应用 ClientHints 映射后返回提供的名称。
* 这用于将 ClientHints 中提供的名称映射到我们使用的名称。
*
* @return String
*/
protected String applyClientHintMapping(String name, Map> clientHintMapping) {
for (Map.Entry> entry : clientHintMapping.entrySet()) {
String mappedName = entry.getKey();
List clientHints = entry.getValue();
for (String clientHint : clientHints) {
if (clientHint.equalsIgnoreCase(name)) {
return mappedName;
}
}
}
return name;
}
/**
* 将 UserAgent 与给定的正则表达式进行匹配。
*
* @param regex 正则表达式
* @return {@code List | null}
* @throws Exception
*/
protected List matchUserAgent(String regex) throws Exception {
// 仅当 UserAgent 以给定的正则表达式开头,或者前面没有字母时才匹配
regex = "(?:^|[^A-Z0-9\\-_]|[^A-Z0-9\\-]_|sprd-|MZ-)(?:" + regex.replace("/", "\\/") + ")";
try {
Matcher matcher = Pattern.compile(regex, Pattern.CASE_INSENSITIVE).matcher(userAgent);
List resultList;
if (matcher.find()) {
resultList = new ArrayList<>();
int groupCount = matcher.groupCount();
for (int i = 0; i <= groupCount; i++) {
resultList.add(matcher.group(i));
}
return resultList;
}
} catch (Exception exception) {
throw new Exception(
String.format("%s\nRegex: %s", exception.getMessage(), regex),
exception
);
}
return null;
}
protected String buildByMatch(String item, List matches) {
List search = new ArrayList<>();
List replace = new ArrayList<>();
for (int nb = 1; nb <= matches.size(); nb++) {
search.add("$" + nb);
try {
replace.add(matches.get(nb));
} catch (Exception e) {
replace.add("");
}
}
String result = item;
for (int i = 0; i < search.size(); i++) {
result = result.replace(search.get(i), replace.get(i));
}
return result.trim();
}
/**
* 使用给定的 versionString 和 matches 构建版本。
*
* 例子:
* $versionString = 'v$2'
* $matches = ['version_1_0_1', '1_0_1']
* 返回值将是 v1.0.1
*
* @param versionString 版本字符串
* @param matches 正则匹配结果
* @return String
*/
protected String buildVersion(String versionString, List matches) {
versionString = buildByMatch(versionString, matches);
versionString = versionString.replace("_", ".");
if (VERSION_TRUNCATION_NONE != maxMinorParts && (versionString.split("\\.").length - 1) > maxMinorParts) {
String[] versionParts = versionString.split("\\.");
versionParts = Arrays.copyOfRange(versionParts, 0, 1 + maxMinorParts);
versionString = String.join(".", versionParts);
}
return trim(versionString, " .");
}
public static String trim(String str, String charsToRemove) {
String regexPattern = "^[" + Pattern.quote(charsToRemove) + "]+|[" + Pattern.quote(charsToRemove) + "]+$";
return str.replaceAll(regexPattern, "");
}
/**
* 对 UserAgent 字符串进行所有正则表达式的组合测试。
*
* getRegexes() 返回的所有正则表达式将被反转并使用 “|” 连接起来,
* 之后将拼接后长长的正则表达式与 UserAgent 字符串进行匹配测试。
*
* 此方法可用于在单个正则表达式进行检查之前通过进行大检查来加快检测速度。
*
* @return {@code List | null}
*/
protected List preMatchOverall(String fixtureFile) throws Exception {
List