A colleague (Brother Tiger) said to me that there must be output every day, and output can make oneself grow.
When memorizing words in a bit of English, watch the English video above and there is a passage like this. If what you do today is the same as what you did yesterday, then what you get is the same as yesterday.
I agree with all the above viewpoints. Share with everyone, hope it will be useful to you too
Stop talking nonsense~ enter the topic
One: Overview of the catalog
Not many classes. This time I can slowly say one by one. There are many classes and codes in the reflection package, so I can only package the source code and upload it to the resources for everyone to download (the download does not require points).
Looking at this picture, I said that this module encapsulates xpath, do you want to ask what xpath is? Click on the link to learn Xpath
Two: XPathParser, PropertyParser, GenericTokenParser these three analysis class analysis.
GenericTokenParser: Look at the comments and code below. This is relatively simple.
/**
* 有的东西看起来特别多的时候,又看不懂的时候,去调试就对了。
* 找单测。去调试
*/
package org.apache.ibatis.parsing;
/**
* 通用的 Token 解析器
*
* @author Clinton Begin
*/
public class GenericTokenParser {
/**
* 开始的 token 字符串
*/
private final String openToken;
/**
* 结束的 token 字符串
*/
private final String closeToken;
/**
* token 处理器
*/
private final TokenHandler handler;
/**
* 构造方法
* @param openToken 开始的token 字符串
* @param closeToken 结束的token字符串
* @param handler token的处理器
*/
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 寻找开始的 openToken 的位置
int start = text.indexOf(openToken);
if (start == -1) {
// 找不到,直接返回
return text;
}
char[] src = text.toCharArray();
// 起始查找位置
int offset = 0;
// 接收结果的字符串构造器
final StringBuilder builder = new StringBuilder();
// 匹配到 openToken 和 closeToken 之间的表达式
StringBuilder expression = null;
do {
// 转义字符
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
// 因为 openToken 前面一个位置是 \ 转义字符,所以忽略 \
// 添加 [offset, start - offset - 1] 和 openToken 的内容,添加到 builder 中
builder.append(src, offset, start - offset - 1).append(openToken);
// 修改 offset
offset = start + openToken.length();
} else {
// found open token. let's search close token.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
builder.append(src, offset, start - offset);
offset = start + openToken.length();
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
// ABCA.indexOf("A",2),从第二个下标开始,查找A第一次出现的位置
// 返回指定字符第一次出现的字符串内的索引,以指定的索引开始搜索。
end = text.indexOf(closeToken, offset);
} else {
// append(str,offset,len) str是一个数组。如果是 lilerong. 这个式子是append("lilerong",2,3), 那么表示从lilerong这个字符中的第2个下标开始,往后截取3个长度。-》ler
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
builder.append(src, start, src.length - start);
offset = src.length;
} else {
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
} while (start > -1);
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
public static void main(String[] args) {
final StringBuilder builder = new StringBuilder();
String text="James T Kirk reporting.";
char[] src = text.toCharArray();
builder.append(src, 2, 3);
System.out.println(builder.toString());
// 输出的是:mes
}
}
package org.apache.ibatis.parsing;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
class GenericTokenParserTest {
public static class VariableTokenHandler implements TokenHandler {
private Map<String, String> variables = new HashMap<>();
VariableTokenHandler(Map<String, String> variables) {
this.variables = variables;
}
@Override
public String handleToken(String content) {
return variables.get(content);
}
}
@Test
void shouldDemonstrateGenericTokenReplacement() {
GenericTokenParser parser = new GenericTokenParser("${", "}", new VariableTokenHandler(new HashMap<String, String>() {
{
put("first_name", "James");
put("initial", "T");
put("last_name", "Kirk");
put("var{with}brace", "Hiya");
put("", "");
}
}));
assertEquals("James T Kirk reporting.", parser.parse("${first_name} ${initial} ${last_name} reporting."));
}
}
PropertyParser: This class still uses a general token processor, the code is still relatively simple, and the single test class inside can be understood.
/**
* Copyright 2009-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.parsing;
import java.util.Properties;
/**
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class PropertyParser {
private static final String KEY_PREFIX = "org.apache.ibatis.parsing.PropertyParser.";
/**
* The special property key that indicate whether enable a default value on placeholder.
* <p>
* The default value is {@code false} (indicate disable a default value on placeholder)
* If you specify the {@code true}, you can specify key and default value on placeholder (e.g. {@code ${db.username:postgres}}).
* </p>
* @since 3.4.2
*/
public static final String KEY_ENABLE_DEFAULT_VALUE = KEY_PREFIX + "enable-default-value";
/**
* The special property key that specify a separator for key and default value on placeholder.
* <p>
* The default separator is {@code ":"}.
* </p>
* @since 3.4.2
*/
public static final String KEY_DEFAULT_VALUE_SEPARATOR = KEY_PREFIX + "default-value-separator";
/**
* 默认分隔符是否可用
*/
private static final String ENABLE_DEFAULT_VALUE = "false";
/**
* 默认分隔符
*/
private static final String DEFAULT_VALUE_SEPARATOR = ":";
private PropertyParser() {
// Prevent Instantiation
}
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 调用通用的token 解析器: 开始的 token 字符串${,结束的 token 字符串:},使用的处理器为 VariableTokenHandler
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
/**
* 可变 token 处理器
*/
private static class VariableTokenHandler implements TokenHandler {
private final Properties variables;
// 是否使用默认分隔符: 默认false。
private final boolean enableDefaultValue;
// 默认分隔符
private final String defaultValueSeparator;
private VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
private String getPropertyValue(String key, String defaultValue) {
return (variables == null) ? defaultValue : variables.getProperty(key, defaultValue);
}
@Override
public String handleToken(String content) {
if (variables != null) {
String key = content;
if (enableDefaultValue) {
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
return variables.getProperty(key, defaultValue);
}
}
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
return "${" + content + "}";
}
}
}
/**
* Copyright 2009-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.parsing;
import java.util.Properties;
import org.assertj.core.api.Assertions;
import org.junit.jupiter.api.Test;
class PropertyParserTest {
@Test
void replaceToVariableValue() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
props.setProperty("key", "value");
props.setProperty("tableName", "members");
props.setProperty("orderColumn", "member_id");
props.setProperty("a:b", "c");
Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("value");
Assertions.assertThat(PropertyParser.parse("${key:aaaa}", props)).isEqualTo("value");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM members ORDER BY member_id");
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");
props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("c");
}
@Test
void notReplace() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
Assertions.assertThat(PropertyParser.parse("${key}", props)).isEqualTo("${key}");
Assertions.assertThat(PropertyParser.parse("${key}", null)).isEqualTo("${key}");
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "false");
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");
props.remove(PropertyParser.KEY_ENABLE_DEFAULT_VALUE);
Assertions.assertThat(PropertyParser.parse("${a:b}", props)).isEqualTo("${a:b}");
}
@Test
void applyDefaultValue() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
Assertions.assertThat(PropertyParser.parse("${key:default}", props)).isEqualTo("default");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${tableName:users} ORDER BY ${orderColumn:id}", props)).isEqualTo("SELECT * FROM users ORDER BY id");
Assertions.assertThat(PropertyParser.parse("${key:}", props)).isEmpty();
Assertions.assertThat(PropertyParser.parse("${key: }", props)).isEqualTo(" ");
Assertions.assertThat(PropertyParser.parse("${key::}", props)).isEqualTo(":");
}
@Test
void applyCustomSeparator() {
Properties props = new Properties();
props.setProperty(PropertyParser.KEY_ENABLE_DEFAULT_VALUE, "true");
props.setProperty(PropertyParser.KEY_DEFAULT_VALUE_SEPARATOR, "?:");
Assertions.assertThat(PropertyParser.parse("${key?:default}", props)).isEqualTo("default");
Assertions.assertThat(PropertyParser.parse("SELECT * FROM ${schema?:prod}.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}", props)).isEqualTo("SELECT * FROM prod.${tableName == null ? 'users' : tableName} ORDER BY ${orderColumn}");
Assertions.assertThat(PropertyParser.parse("${key?:}", props)).isEmpty();
Assertions.assertThat(PropertyParser.parse("${key?: }", props)).isEqualTo(" ");
Assertions.assertThat(PropertyParser.parse("${key?::}", props)).isEqualTo(":");
}
}
/**
* Copyright 2009-2019 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.parsing;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.apache.ibatis.builder.BuilderException;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.EntityResolver;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
/**
* 用来解析mybatis-config.xml 和**Mapper.xml等xml配置文件
*
* @author Clinton Begin
* @author Kazuki Shimizu
*/
public class XPathParser {
/**
* xml document 对象
*/
private final Document document;
/**
* 是否校验
*/
private boolean validation;
/**
* xml 实体解析器
*/
private EntityResolver entityResolver;
/**
* 变量properties 对象
*/
private Properties variables;
/**
* java xpath 对象
*/
private XPath xpath;
public XPathParser(String xml) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream) {
commonConstructor(false, null, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document) {
commonConstructor(false, null, null);
this.document = document;
}
public XPathParser(String xml, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation) {
commonConstructor(validation, null, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation) {
commonConstructor(validation, null, null);
this.document = document;
}
public XPathParser(String xml, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation, Properties variables) {
commonConstructor(validation, variables, null);
this.document = document;
}
/**
* 构造 XPathParser 对象
* @param xml xml 文件地址
* @param validation 是否校验xml
* @param variables 变量properties对象
* @param entityResolver xml 实体解析器
*/
public XPathParser(String xml, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(new StringReader(xml)));
}
public XPathParser(Reader reader, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(reader));
}
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
public XPathParser(Document document, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = document;
}
public void setVariables(Properties variables) {
this.variables = variables;
}
public String evalString(String expression) {
return evalString(document, expression);
}
public String evalString(Object root, String expression) {
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
public Boolean evalBoolean(String expression) {
return evalBoolean(document, expression);
}
public Boolean evalBoolean(Object root, String expression) {
return (Boolean) evaluate(expression, root, XPathConstants.BOOLEAN);
}
public Short evalShort(String expression) {
return evalShort(document, expression);
}
public Short evalShort(Object root, String expression) {
return Short.valueOf(evalString(root, expression));
}
public Integer evalInteger(String expression) {
return evalInteger(document, expression);
}
public Integer evalInteger(Object root, String expression) {
return Integer.valueOf(evalString(root, expression));
}
public Long evalLong(String expression) {
return evalLong(document, expression);
}
public Long evalLong(Object root, String expression) {
return Long.valueOf(evalString(root, expression));
}
public Float evalFloat(String expression) {
return evalFloat(document, expression);
}
public Float evalFloat(Object root, String expression) {
return Float.valueOf(evalString(root, expression));
}
public Double evalDouble(String expression) {
return evalDouble(document, expression);
}
public Double evalDouble(Object root, String expression) {
return (Double) evaluate(expression, root, XPathConstants.NUMBER);
}
public List<XNode> evalNodes(String expression) {
return evalNodes(document, expression);
}
public List<XNode> evalNodes(Object root, String expression) {
List<XNode> xnodes = new ArrayList<>();
NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
xnodes.add(new XNode(this, nodes.item(i), variables));
}
return xnodes;
}
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
/**
* 获取指定元素或者节点的值
* @param expression 表达式
* @param root 指定节点
* @param returnType 返回类型
* @return 值
*/
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
/**
* 创建document 对象
* @param inputSource inputSource xml的inputSource 对象
* @return document 对象
*/
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
// 1: 创建 DocumentBuilderFactory 对象
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
// 2: 创建 DocumentBuilder对象
DocumentBuilder builder = factory.newDocumentBuilder();
// 设置实体解析器
builder.setEntityResolver(entityResolver);
// 实现都是空的
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
// NOP
}
});
// 3: 解析xml文件
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
/**
* 公用的构造方法逻辑。代码如下
* @param validation
* @param variables
* @param entityResolver
*/
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
}
From my own look, this is the value that can be obtained and parsed according to the address, which is equivalent to enhancing xpath. In fact, where is the enhancement. I haven't found it yet. Just like the transmitter class in the previous article. I may read the code to understand several levels. But I don't understand why the packaging is constantly being packaged.
It feels troublesome to look at it. Maybe my current views are just that I don't have much foresight. It's like a frog at the bottom of a well. My vision is still small. So I can't understand it.
Keep going~
Summary: The code for the parser template is here. I wrote this just to record a process I saw. If you happen to be doing this, I hope it will help you. If there is something wrong and need to be corrected, I hope everyone will raise some points. Thank you