
sf.tools.IOUtils Maven / Gradle / Ivy
Show all versions of sorm Show documentation
package sf.tools;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sf.tools.io.UnicodeReader;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.CharArrayWriter;
import java.io.Closeable;
import java.io.File;
import java.io.FileFilter;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.net.URL;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
public class IOUtils {
private static final Logger logger = LoggerFactory.getLogger(IOUtils.class);
private static final int DEFAULT_BUFFER_SIZE = 4096;
private static final File[] EMPTY = new File[0];
/**
* 关闭指定的对象,不会抛出异常
* @param input 需要关闭的资源
*/
public static void closeQuietly(Closeable input) {
if (input != null) {
try {
input.close();
} catch (IOException e) {
logger.error("", e);
}
}
}
/**
* 将指定的流保存为临时文件
* @param is
* @return
* @throws IOException
*/
public static File saveAsTempFile(InputStream is) throws IOException {
File f = File.createTempFile("~tmp", ".io");
saveAsFile(f, is);
return f;
}
/**
* 将数据从来源保存为临时文件
* @param reader
* @return
* @throws IOException
*/
public static File saveAsTempFile(Reader reader) throws IOException {
File f = File.createTempFile("~tmp", ".io");
saveAsFile(f, null, reader);
return f;
}
/**
* 将输入流保存为文件
* @param file
* @param iss
* @throws IOException
*/
public static void saveAsFile(File file, InputStream... iss)
throws IOException {
ensureParentFolder(file);
BufferedOutputStream os = new BufferedOutputStream(
new FileOutputStream(file));
try {
for (InputStream is : iss) {
copy(is, os, false);
}
} finally {
os.flush();
os.close();
}
}
/**
* 检查/创建文件在所的文件夹
* @param file
*/
public static void ensureParentFolder(File file) {
File f = file.getParentFile();
if (f != null && !f.exists()) {
f.mkdirs();
} else if (f != null && f.isFile()) {
throw new RuntimeException(f.getAbsolutePath()
+ " is a exist file, can't create directory.");
}
}
/*
* Copies the contents of the given {@link InputStream} to the given {@link
* OutputStream}.
*
* @param pIn The input stream, which is being read. It is guaranteed, that
* {@link InputStream#close()} is called on the stream.
* 关于InputStram在何时关闭的问题,我一直认为应当是成对操作的(即在哪个方法中生成Stream,就要在使用完后关闭),
* 因此不打算在这里使用close方法。 但是后来我又考虑到,InputStream在使用完后,其内部标记已经发生了变化,无法再次使用。
* (reset方法的效果和实现有关,并不能保证回复到Stream使用前的状态。)
* 因此考虑这里统一关闭以防止疏漏,外面再关一次也不会有问题(作为好习惯,还是应该成对打开和关闭)。
*
* @param pOut 输出流,可以为null,此时输入流中的相应数据将丢弃
*
* @param pClose True guarantees, that {@link OutputStream#close()} is
* called on the stream. False indicates, that only {@link
* OutputStream#flush()} should be called finally.
*
* @param pBuffer Temporary buffer, which is to be used for copying data.
*
* @return Number of bytes, which have been copied.
*
* @throws IOException An I/O error occurred.
*/
private static long copy(InputStream in, OutputStream out, boolean inClose,
boolean outClose, byte[] pBuffer) throws IOException {
if (in == null)
throw new NullPointerException();
long total = 0;
try {
int res;
while ((res = in.read(pBuffer)) != -1) {
if (out != null) {
out.write(pBuffer, 0, res);
}
total += res;
}
if (out != null)
out.flush();
} finally {
if (outClose)
closeQuietly(out);
if (inClose)
closeQuietly(in);
}
return total;
}
/*
* 同上、READER和Writer之间的拷贝
*/
private static long copy(Reader in, Writer out, boolean inClose,
boolean outClose, char[] pBuffer) throws IOException {
if (in == null)
throw new NullPointerException();
long total = 0;
try {
int res;
while ((res = in.read(pBuffer)) != -1) {
if (out != null) {
out.write(pBuffer, 0, res);
}
total += res;
}
if (out != null)
out.flush();
} finally {
if (outClose && out != null)
closeQuietly(out);
if (inClose)
closeQuietly(in);
}
return total;
}
/**
* 流之间拷贝
* @param in 输入
* @param out 输出
* @param inClose 关闭输入流?
* @param outClose 关闭输出流?
* @return
* @throws IOException
*/
public static long copy(InputStream in, OutputStream out, boolean inClose,
boolean outClose) throws IOException {
return copy(in, out, inClose, outClose, new byte[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
* @param in 输入
* @param out 输出
* @param inClose 关闭输入流
* @param outClose 关闭输出流
* @return
* @throws IOException
*/
public static long copy(Reader in, Writer out, boolean inClose,
boolean outClose) throws IOException {
return copy(in, out, inClose, outClose, new char[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
* @param in 输入
* @param out 输出
* @param pClose 关闭输出流?
* @return 拷贝长度
* @throws IOException
*/
public static long copy(Reader in, Writer out, boolean pClose)
throws IOException {
return copy(in, out, true, pClose, new char[DEFAULT_BUFFER_SIZE]);
}
/**
* 流之间拷贝
* @param in 输入
* @param out 输出
* @param closeOutStream 关闭输出流? (输入流默认关闭)
* @return
* @throws IOException
*/
public static long copy(InputStream in, OutputStream out,
boolean closeOutStream) throws IOException {
return copy(in, out, true, closeOutStream,
new byte[DEFAULT_BUFFER_SIZE]);
}
/**
* 将制定的URL中的数据读出成byte[]
* @param url 资源目标位置
* @return 字节数组
* @throws IOException IO操作异常
*/
public static byte[] toByteArray(URL url) throws IOException {
return toByteArray(url.openStream());
}
/**
* 读取文件到内存(不可用于大文件)
* @param file 本地文件
* @return 字节数组
* @throws IOException IO操作异常
*/
public static byte[] toByteArray(File file) throws IOException {
InputStream in = new FileInputStream(file);
try {
return toByteArray(in, (int) file.length());
} finally {
in.close();
}
}
/**
* 读取文件到内存(不可用于大文件)
* @param file
* @return
* @throws IOException
* @deprecated use {@linkp #toByteArray(File)}
*/
public static byte[] asByteArray(File file) throws IOException {
return toByteArray(file);
}
/**
* 读取流数据到内存。注意这个方法会将数据流全部读入到内存中,因此不适用于很大的数据对象
* @param in
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream in) throws IOException {
try {
return toByteArray(in, -1);
} finally {
in.close();
}
}
/**
* 从流中读取指定的字节,第三个版本,性能再度提升 参考数据,从120M文件中读取前60M,此方法耗时125ms,v2耗时156ms
* @param in
* @param length 要读取的字节数,-1表示不限制。(注意实际处理中-1的情况下最多读取2G数据,超过2G不会读取)
* @return
* @throws IOException
*/
public static byte[] toByteArray(InputStream in, int length)
throws IOException {
ByteArrayOutputStream out;
if (length > 0) {
out = new ByteArrayOutputStream(length);
} else {
out = new ByteArrayOutputStream(1024);
}
int buf = DEFAULT_BUFFER_SIZE;
byte[] pBuffer = new byte[buf];
int left = (length > 0) ? length : Integer.MAX_VALUE;// 剩余字节数
while (left >= buf) {
int n = in.read(pBuffer);
if (n == -1) {
left = 0;
break;
}
left -= n;
out.write(pBuffer, 0, n);
}
while (left > 0) {
int n = in.read(pBuffer, 0, left);
if (n == -1) {
break;
}
left -= n;
out.write(pBuffer, 0, n);
}
out.close();// ByteArrayOut其实是不需要close的,这里close是为了防止一些代码检查工具提出警告
return out.toByteArray();
}
/**
* 将内存数据块写入文件
* @param file
* @param data
* @throws IOException
*/
public static void saveAsFile(File file, byte[] data) throws IOException {
saveAsFile(file, false, data);
}
public static void saveAsFile(File file, String... texts) throws IOException {
saveAsFile(file, null, texts);
}
/**
* 将reader内容保存为文件
* @param file
* @param charset
* @param readers
* @throws IOException
*/
public static void saveAsFile(File file, Charset charset, Reader... readers)
throws IOException {
BufferedWriter os = getWriter(file,
charset == null ? null : charset.name(), false);
try {
for (Reader reader : readers) {
copy(reader, os, true, false, new char[2048]);
}
} finally {
closeQuietly(os);
}
}
/**
* 将内存数据块写入文件
* @param data
* @param file
* @throws IOException
*/
public static void saveAsFile(File file, boolean append, byte[] data)
throws IOException {
ensureParentFolder(file);
OutputStream out = null;
try {
out = new FileOutputStream(file, append);
out.write(data);
} finally {
if (out != null) {
out.flush();
out.close();
}
}
}
/**
* 将文字写入文件
* @param file
* @param charset
* @param texts
* @throws IOException
*/
public static void saveAsFile(File file, Charset charset, String... texts) throws IOException {
BufferedWriter os = getWriter(file, charset, false);
try {
for (String text : texts) {
os.write(text);
}
} finally {
if (os != null) {
os.flush();
os.close();
}
}
}
/**
* 获得二进制文件写入句柄
* @Title: getInputStream
*/
public static BufferedInputStream getInputStream(File file) {
try {
return new BufferedInputStream(new FileInputStream(file));
} catch (IOException e) {
throw new IllegalArgumentException(e);
}
}
/**
* 获得文本文件写入流
* @param target
* @param charSet
* @param append
* @return
* @throws IOException
*/
public static BufferedWriter getWriter(File target, Charset charSet, boolean append) {
ensureParentFolder(target);
try {
OutputStream os = new FileOutputStream(target, append);
if (charSet == null)
charSet = Charset.defaultCharset();
OutputStreamWriter osw = new OutputStreamWriter(os, charSet);
return new BufferedWriter(osw);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获得文本文件写入流
* @param target
* @param charSet
* @param append
* @return
* @throws IOException
*/
public static BufferedWriter getWriter(File target, String charSet,
boolean append) {
ensureParentFolder(target);
try {
OutputStream os = new FileOutputStream(target, append);
if (charSet == null)
charSet = Charset.defaultCharset().name();
OutputStreamWriter osw = new OutputStreamWriter(os, charSet);
return new BufferedWriter(osw);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 从URL获得reader
* @param file
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(URL file, String charSet) {
if (file == null)
return null;
try {
InputStream is = file.openStream();
InputStreamReader isr = new InputStreamReader(is, charSet);
return new BufferedReader(isr);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* 获得一个供读取文本的reader, 本方法可以从BOM自动判断utf-8, unicode等类型,因此charset一般可以为null.
* 只有当文件为不带BOM的unicode时才需要指定。
* @param file
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(File file, String charSet) throws IOException {
if (file == null)
return null;
InputStream is = new FileInputStream(file);
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* 获得Reader
* @param is
* @param charSet
* @return
* @throws IOException
*/
public static BufferedReader getReader(InputStream is, String charSet) {
if (is == null)
return null;
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* 获得相对于一个class的所在路径的相对路径的文件资源
* @param source class
* @param fileName 文件相对路径
* @param charSet 编码
* @return BufferedReader 如果文件不存在,返回null
*/
public static BufferedReader getReader(Class> source, String fileName, String charSet) {
InputStream is = source.getResourceAsStream(fileName);
if (is == null) {
is = source.getClassLoader().getResourceAsStream(toClassLoaderResourcePath(source, fileName));
}
if (is == null)
return null;
UnicodeReader isr = new UnicodeReader(is, charSet);
return new BufferedReader(isr);
}
/**
* ClassLoader resource不用/开头。这里将c转换过去
* @param fileName
* @return
*/
public static String toClassLoaderResourcePath(Class> c, String fileName) {
if (fileName.startsWith("/"))
return fileName.substring(1);
String path = c.getPackage().getName().replace('.', '/');
return path.concat("/").concat(fileName);
}
/**
* 文件过滤器
* @author
*/
public static abstract class FileFilterEx implements FileFilter {
/**
* 是否跳出当前文件夹搜索 每次运行完成accept方法后,程序会执行此方法,如果返回true则将停止在此目录中的搜索。
* @return true中断文件搜索,false继续搜索。
*/
protected boolean breakFolder(File root) {
return false;
}
;
}
/**
* 在指定目录下搜索单个文件
* @param root
* @param filter 过滤条件
* @return File 返回文件
* @Title: findFile
*/
public static File findFile(File root, FileFilterEx filter) {
if (root == null || !root.exists())
return null;
boolean breakThisFolder = false;
for (File f : root.listFiles()) {
if (!breakThisFolder) {
if (filter.accept(f)) {
return f;
}
breakThisFolder = filter.breakFolder(root);
}
if (f.isDirectory()) {
File result = findFile(f, filter);
if (result != null)
return result;
}
}
return null;
}
/**
* 在指定目录下搜索文件
* @param root 要搜索的目录
* @param filter 文件过滤器
* @return 搜索到的所有文件
*/
public static Collection findFiles(File root, FileFilterEx filter) {
if (root == null || !root.exists())
return null;
List result = new ArrayList();
boolean breakThisFolder = false;
for (File f : root.listFiles()) {
if (!breakThisFolder) {
if (filter.accept(f)) {
result.add(f);
}
breakThisFolder = filter.breakFolder(root);
}
if (f.isDirectory()) {
result.addAll(findFiles(f, filter));
}
}
return result;
}
/**
* 在指定目录下搜索单个文件
* @param root 要搜索的目录
* @param name 搜索的文件名称(完全匹配)
* @param acceptFolder 是否搜索文件夹
* @return File 返回文件
*/
public static File findFile(File root, final String name, final boolean acceptFolder) {
return findFile(root, new FileFilterEx() {
public boolean accept(File pathname) {
if (!acceptFolder && pathname.isDirectory())
return false;
return pathname.getName().equals(name);
}
});
}
/**
* 将Reader内容读取到内存中的charArray
* @param reader 输入
* @return
* @throws IOException
*/
public static char[] asCharArray(Reader reader) throws IOException {
CharArrayWriter cw = new CharArrayWriter(256);
char[] buf = new char[1024];
int n;
try {
while ((n = reader.read(buf)) > -1) {
cw.write(buf, 0, n);
}
} finally {
reader.close();
}
return cw.toCharArray();
}
/**
* 将Reader的内容读取为一个字符串
* @param reader
* @return
* @throws IOException
*/
public static String asString(Reader reader) throws IOException {
return asString(reader, true);
}
/**
* 将Reader内容读取为字符串
* @param reader
* @param close 关闭reader
* @return
* @throws IOException
*/
public static String asString(Reader reader, boolean close) throws IOException {
if (reader == null)
return null;
StringBuilder sb = new StringBuilder(128);
char[] buf = new char[1024];
int n;
try {
while ((n = reader.read(buf)) > -1) {
sb.append(buf, 0, n);
}
} finally {
if (close)
reader.close();
}
return sb.toString();
}
public static void asStringLambda(URL url, Charset charset, Function func) throws IOException {
if (url == null)
return;
asStringLambda(new BufferedReader(new InputStreamReader(url.openStream(), charset)), true, func);
}
public static void asStringLambda(BufferedReader reader, boolean close, Function func) throws IOException {
if (reader == null)
return;
String line = null;
try {
while ((line = reader.readLine()) != null) {
if (!func.apply(line)) {
break;
}
}
} finally {
if (close)
reader.close();
}
}
public static void asStringLambda(InputStream in, Charset charset, boolean close, Function func) throws IOException {
if (in == null)
return;
String line = null;
BufferedReader reader = new BufferedReader(new InputStreamReader(in, charset));
try {
while ((line = reader.readLine()) != null) {
if (!func.apply(line)) {
break;
}
}
} finally {
if (close)
reader.close();
}
}
public static String asString(File pStream, String charset) throws IOException {
return asString(getReader(pStream, charset));
}
/**
* 将指定位置的数据读出成为文本
* @param url 资源位置
* @param charset 字符编码,可以传入null
* @return 读到的文本
* @throws IOException IO操作异常
**/
public static String asString(URL url, String charset) throws IOException {
if (url == null)
return null;
return asString(url.openStream(), charset, true);
}
/**
* 将输入流转化为String .(使用缺省的字符集)
*
* 最简单的获取系统资源转换为String的写法如下:
* IOUtils.asString(ClassLoader.getSystemResourceAsStream(filename))
*
*/
public static String asString(InputStream pStream) throws IOException {
return asString(pStream, null, true);
}
/**
* 将输入流转化为String
* @param pStream The input stream to read.
* @param pEncoding The character encoding, typically "UTF-8".
* @param close close the in stream?
*/
public static String asString(InputStream pStream, String pEncoding, boolean close) throws IOException {
if (pStream == null)
return null;
ByteArrayOutputStream baos = new ByteArrayOutputStream(256);
copy(pStream, baos, close);
if (pEncoding == null) {
return baos.toString();
} else {
return baos.toString(pEncoding);
}
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
*
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
*
* - 在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。
* - Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。
* - Properties中的数据是乱序的,无法保持原先在文件中出现的顺序
* - Properties保留了基于InputStream的接口,使用时容易出现编码错误
*
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* 彻底淘汰落后的java.util.Properties
* @param in 要读取的资源
* @return 文件中的键值对信息。
*/
public static Map loadProperties(URL in) {
return loadProperties(in, false);
}
public static Map loadProperties(URL in, Boolean supportSection) {
Map result = new LinkedHashMap();
loadProperties(getReader(in, null), result, supportSection);
return result;
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
*
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
*
* - 在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。
* - Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。
* - Properties中的数据是乱序的,无法保持原先在文件中出现的顺序
* - Properties保留了基于InputStream的接口,使用时容易出现编码错误
*
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* 彻底淘汰落后的java.util.Properties
* @param in 要读取的数据流。注意读取完成后流会被关闭。
* @return 文件中的键值对信息。
*/
public static Map loadProperties(Reader in) {
return loadProperties(in, false);
}
/**
* 获得配置文件的项目。配置文件用= :等分隔对,语法同properties文件
*
* 使用此方法可以代替使用JDK中的{@link java.util.Properties}工具。因为Properties操作中往往有以下不便
*
* - 在遍历时,由于Properties继承了Map<Object,Object>泛型,不得不编写强制类型转换的代码。
* - Properties继承了Hashtable性能低下,此外如果getProperty(null)还会抛出异常。
* - Properties中的数据是乱序的,无法保持原先在文件中出现的顺序
* - Properties保留了基于InputStream的接口,使用时容易出现编码错误
*
* 因此,建议在加载.properties文件时,不要使用JDK中的{@link java.util.Properties}。
* 彻底淘汰落后的java.util.Properties
* @param in 要读取的数据流。注意读取完成后流会被关闭。
* @param supportSection 支持分节。window下有一种很类似properties格式的配置文件INI。INI文件中可以使用[section]
* 对配置划分小节。开启此开关后,解析时会将当前节名称和配置名拼在一起。 形成 {@code 节名|配置名}的格式。
* @return 文件中的键值对信息。
*/
public static Map loadProperties(Reader in, Boolean supportSection) {
Map result = new LinkedHashMap();
loadProperties(in, result, supportSection);
return result;
}
/*
* Read in a "logical line" from an InputStream/Reader, skip all comment and
* blank lines and filter out those leading whitespace characters ( , and )
* from the beginning of a "natural line". Method returns the char length of
* the "logical line" and stores the line in "lineBuf".
*/
static final class LineReader {
private char[] inCharBuf;
private char[] lineBuf = new char[1024];
private int inLimit = 0;
private int inOff = 0;
private Reader reader;
public LineReader(Reader reader) {
this.reader = reader;
inCharBuf = new char[8192];
}
int readLine() throws IOException {
int len = 0;
char c = 0;
boolean skipWhiteSpace = true;
boolean isCommentLine = false;
boolean isNewLine = true;
boolean appendedLineBegin = false;
boolean precedingBackslash = false;
boolean skipLF = false;
while (true) {
if (inOff >= inLimit) {
inLimit = reader.read(inCharBuf);
inOff = 0;
if (inLimit <= 0) {
if (len == 0 || isCommentLine) {
return -1;
}
return len;
}
}
c = inCharBuf[inOff++];
if (skipLF) {
skipLF = false;
if (c == '\n') {
continue;
}
}
if (skipWhiteSpace) {
if (c == ' ' || c == '\t' || c == '\f') {
continue;
}
if (!appendedLineBegin && (c == '\r' || c == '\n')) {
continue;
}
skipWhiteSpace = false;
appendedLineBegin = false;
}
if (isNewLine) {
isNewLine = false;
if (c == '#' || c == '!') {
isCommentLine = true;
continue;
}
}
if (c != '\n' && c != '\r') {
lineBuf[len++] = c;
if (len == lineBuf.length) {
int newLength = lineBuf.length * 2;
if (newLength < 0) {
newLength = Integer.MAX_VALUE;
}
char[] buf = new char[newLength];
System.arraycopy(lineBuf, 0, buf, 0, lineBuf.length);
lineBuf = buf;
}
// flip the preceding backslash flag
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
} else {
// reached EOL
if (isCommentLine || len == 0) {
isCommentLine = false;
isNewLine = true;
skipWhiteSpace = true;
len = 0;
continue;
}
if (inOff >= inLimit) {
inLimit = reader.read(inCharBuf);
inOff = 0;
if (inLimit <= 0) {
return len;
}
}
if (precedingBackslash) {
len -= 1;
// skip the leading whitespace characters in following
// line
skipWhiteSpace = true;
appendedLineBegin = true;
precedingBackslash = false;
if (c == '\r') {
skipLF = true;
}
} else {
return len;
}
}
}
}
}
/*
* 2015-9-14日修改。为了兼容windows应用ini的结构(带小节) 故将小节用 | 符号添加在每个key前方
*
* @param lr
*
* @param map
*
* @throws IOException
*/
private static void load0(LineReader lr, Map map, Boolean supportSection) throws IOException {
char[] convtBuf = new char[1024];
int limit;
int keyLen;
int valueStart;
char c;
boolean hasSep;
boolean precedingBackslash;
String currentSection = null;
while ((limit = lr.readLine()) >= 0) {
c = 0;
keyLen = 0;
valueStart = limit;
hasSep = false;
precedingBackslash = false;
while (keyLen < limit) {
c = lr.lineBuf[keyLen];
// need check if escaped.
if ((c == '=' || c == ':') && !precedingBackslash) {
valueStart = keyLen + 1;
hasSep = true;
break;
} else if ((c == ' ' || c == '\t' || c == '\f') && !precedingBackslash) {
valueStart = keyLen + 1;
break;
}
if (c == '\\') {
precedingBackslash = !precedingBackslash;
} else {
precedingBackslash = false;
}
keyLen++;
}
while (valueStart < limit) {
c = lr.lineBuf[valueStart];
if (c != ' ' && c != '\t' && c != '\f') {
if (!hasSep && (c == '=' || c == ':')) {
hasSep = true;
} else {
break;
}
}
valueStart++;
}
String key = loadConvert(lr.lineBuf, 0, keyLen, convtBuf);
String value = loadConvert(lr.lineBuf, valueStart, limit - valueStart, convtBuf);
if (supportSection == null) {
supportSection = isSection(key);
}
if (supportSection && value.length() == 0 && isSection(key)) {
currentSection = key.length() > 2 ? key.substring(1, key.length() - 1) : null;
} else {
if (currentSection == null) {
map.put(key, value);
} else {
map.put(currentSection + "|" + key, value);
}
}
}
}
private static boolean isSection(String key) {
if (key == null || key.length() < 2) {
return false;
}
return key.charAt(0) == '[' && key.charAt(key.length() - 1) == ']';
}
/*
* Converts encoded \uxxxx to unicode chars and changes special saved
* chars to their original forms
*/
private static String loadConvert(char[] in, int off, int len, char[] convtBuf) {
if (convtBuf.length < len) {
int newLen = len * 2;
if (newLen < 0) {
newLen = Integer.MAX_VALUE;
}
convtBuf = new char[newLen];
}
char aChar;
char[] out = convtBuf;
int outLen = 0;
int end = off + len;
while (off < end) {
aChar = in[off++];
if (aChar == '\\') {
aChar = in[off++];
if (aChar == 'u') {
// Read the xxxx
int value = 0;
for (int i = 0; i < 4; i++) {
aChar = in[off++];
switch (aChar) {
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
value = (value << 4) + aChar - '0';
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
value = (value << 4) + 10 + aChar - 'a';
break;
case 'A':
case 'B':
case 'C':
case 'D':
case 'E':
case 'F':
value = (value << 4) + 10 + aChar - 'A';
break;
default:
throw new IllegalArgumentException("Malformed \\uxxxx encoding.");
}
}
out[outLen++] = (char) value;
} else {
if (aChar == 't')
aChar = '\t';
else if (aChar == 'r')
aChar = '\r';
else if (aChar == 'n')
aChar = '\n';
else if (aChar == 'f')
aChar = '\f';
out[outLen++] = aChar;
}
} else {
out[outLen++] = aChar;
}
}
return new String(out, 0, outLen);
}
private static String saveConvert(String theString, boolean isKey, int option) {
if (isKey && option < 0) {
return theString;
} else if (isKey == false && option < 1) {
return theString;
}
int len = theString.length();
int bufLen = len * 2;
if (bufLen < 0) {
bufLen = Integer.MAX_VALUE;
}
StringBuilder outBuffer = new StringBuilder(bufLen);
for (int x = 0; x < len; x++) {
char aChar = theString.charAt(x);
// Handle common case first, selecting largest block that
// avoids the specials below
if ((aChar > 61) && (aChar < 127)) {
if (aChar == '\\') {
outBuffer.append('\\');
outBuffer.append('\\');
continue;
}
outBuffer.append(aChar);
continue;
}
switch (aChar) {
case ' ':
if (x == 0 || isKey)
outBuffer.append('\\');
outBuffer.append(' ');
break;
case '\t':
outBuffer.append('\\');
outBuffer.append('t');
break;
case '\n':
outBuffer.append('\\');
outBuffer.append('n');
break;
case '\r':
outBuffer.append('\\');
outBuffer.append('r');
break;
case '\f':
outBuffer.append('\\');
outBuffer.append('f');
break;
case '=': // Fall through
case ':': // Fall through
case '#': // Fall through
case '!':
outBuffer.append('\\');
outBuffer.append(aChar);
break;
default:
outBuffer.append(aChar);
}
}
return outBuffer.toString();
}
/*
* 内部使用,properties文件读取
*/
static final void loadProperties(Reader in, Map map, Boolean supportSecion) {
if (in == null)
return;
try {
load0(new LineReader(in), map, supportSecion);
} catch (Exception e1) {
logger.error("", e1);
} finally {
closeQuietly(in);
}
}
/**
* 得到文件的扩展名(小写如果没有则返回空字符串)。如果传入的文件名包含路径,分析时会考虑最后一个\或/字符后满的部分才作为文件名。
* @param fileName
* @return
*/
public static String getExtName(String fileName) {
int dashIndex1 = fileName.lastIndexOf('/');
int dashIndex2 = fileName.lastIndexOf('\\');
int dash = Math.max(dashIndex1, dashIndex2);// 获得最后一个斜杠的位置
int pos = fileName.lastIndexOf(".");
if (pos > -1 && pos > dash) {
return fileName.substring(pos + 1).toLowerCase();
} else {
return "";
}
}
/**
* 得到文件名除去扩展名的部分。如果传入的文件名包含路径,分析时会考虑最后一个\或/字符后满的部分才作为文件名。 去除扩展名后返回包含路径的部分。
* @param fileName
* @return
*/
public static String removeExt(String fileName) {
int dashIndex1 = fileName.lastIndexOf('/');
int dashIndex2 = fileName.lastIndexOf('\\');
int dash = Math.max(dashIndex1, dashIndex2);// 获得最后一个斜杠的位置
int pos = fileName.lastIndexOf('.');
if (pos > -1 && pos > dash) {
return fileName.substring(0, pos);
}
return fileName;
}
}