diff --git a/src/main/java/net/ipip/ipdb/BaseStation.java b/src/main/java/net/ipip/ipdb/BaseStation.java index da1eda9..2ea996e 100644 --- a/src/main/java/net/ipip/ipdb/BaseStation.java +++ b/src/main/java/net/ipip/ipdb/BaseStation.java @@ -1,5 +1,6 @@ package net.ipip.ipdb; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -14,7 +15,7 @@ public class BaseStation { private Reader reader; public BaseStation(String name) throws IOException,InvalidDatabaseException { - this.reader = new Reader(name); + this.reader = new RandomAccessReader(name); } public BaseStation(InputStream in) throws IOException, InvalidDatabaseException { @@ -23,8 +24,11 @@ public BaseStation(InputStream in) throws IOException, InvalidDatabaseException public boolean reload(String name) { try { - Reader r = new Reader(name); - this.reader = r; + Reader old = this.reader; + this.reader = new RandomAccessReader(name); + if (old instanceof Closeable) { + ((Closeable) old).close(); + } } catch (Exception e) { return false; } diff --git a/src/main/java/net/ipip/ipdb/City.java b/src/main/java/net/ipip/ipdb/City.java index 7b45914..705fdd7 100644 --- a/src/main/java/net/ipip/ipdb/City.java +++ b/src/main/java/net/ipip/ipdb/City.java @@ -1,6 +1,7 @@ package net.ipip.ipdb; import java.io.InputStream; +import java.io.Closeable; import java.io.IOException; import java.util.Arrays; import java.util.HashMap; @@ -14,7 +15,7 @@ public class City { private Reader reader; public City(String name) throws IOException,InvalidDatabaseException { - this.reader = new Reader(name); + this.reader = new RandomAccessReader(name); } public City(InputStream in) throws IOException,InvalidDatabaseException { @@ -23,8 +24,11 @@ public City(InputStream in) throws IOException,InvalidDatabaseException { public boolean reload(String name) { try { - Reader r = new Reader(name); - this.reader = r; + Reader old = this.reader; + this.reader = new RandomAccessReader(name); + if (old instanceof Closeable) { + ((Closeable) old).close(); + } } catch (Exception e) { return false; } diff --git a/src/main/java/net/ipip/ipdb/CityInfo.java b/src/main/java/net/ipip/ipdb/CityInfo.java index d8a8fd8..17c9957 100644 --- a/src/main/java/net/ipip/ipdb/CityInfo.java +++ b/src/main/java/net/ipip/ipdb/CityInfo.java @@ -249,7 +249,8 @@ public DistrictInfo getDistrictInfo() { if (str == null) { return null; } - Map info = JSONObject.parseObject(str, Map.class); + @SuppressWarnings("unchecked") + Map info = JSONObject.parseObject(str, Map.class); String[] data = new String[8]; data[0] = this.getCountryName(); diff --git a/src/main/java/net/ipip/ipdb/District.java b/src/main/java/net/ipip/ipdb/District.java index df0e05a..e00e906 100644 --- a/src/main/java/net/ipip/ipdb/District.java +++ b/src/main/java/net/ipip/ipdb/District.java @@ -1,5 +1,6 @@ package net.ipip.ipdb; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -14,7 +15,7 @@ public class District { private Reader reader; public District(String name) throws IOException,InvalidDatabaseException { - this.reader = new Reader(name); + this.reader = new RandomAccessReader(name); } public District(InputStream in) throws IOException, InvalidDatabaseException { @@ -23,8 +24,11 @@ public District(InputStream in) throws IOException, InvalidDatabaseException { public boolean reload(String name) { try { - Reader r = new Reader(name); - this.reader = r; + Reader old = this.reader; + this.reader = new RandomAccessReader(name); + if (old instanceof Closeable) { + ((Closeable) old).close(); + } } catch (Exception e) { return false; } diff --git a/src/main/java/net/ipip/ipdb/IDC.java b/src/main/java/net/ipip/ipdb/IDC.java index 783c848..da9ec4f 100644 --- a/src/main/java/net/ipip/ipdb/IDC.java +++ b/src/main/java/net/ipip/ipdb/IDC.java @@ -1,5 +1,6 @@ package net.ipip.ipdb; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -14,7 +15,7 @@ public class IDC { private Reader reader; public IDC(String name) throws IOException,InvalidDatabaseException { - this.reader = new Reader(name); + this.reader = new RandomAccessReader(name); } public IDC(InputStream in) throws IOException, InvalidDatabaseException { @@ -23,8 +24,11 @@ public IDC(InputStream in) throws IOException, InvalidDatabaseException { public boolean reload(String name) { try { - Reader r = new Reader(name); - this.reader = r; + Reader old = this.reader; + this.reader = new RandomAccessReader(name); + if (old instanceof Closeable) { + ((Closeable) old).close(); + } } catch (Exception e) { return false; } diff --git a/src/main/java/net/ipip/ipdb/IPFormatException.java b/src/main/java/net/ipip/ipdb/IPFormatException.java index 8bada35..5291ae4 100644 --- a/src/main/java/net/ipip/ipdb/IPFormatException.java +++ b/src/main/java/net/ipip/ipdb/IPFormatException.java @@ -1,9 +1,12 @@ package net.ipip.ipdb; - public class IPFormatException extends Exception { public IPFormatException(String name) { super(name); } + + public IPFormatException(Throwable cause) { + super(cause); + } } diff --git a/src/main/java/net/ipip/ipdb/InvalidDatabaseException.java b/src/main/java/net/ipip/ipdb/InvalidDatabaseException.java index f3503dc..a8602b8 100644 --- a/src/main/java/net/ipip/ipdb/InvalidDatabaseException.java +++ b/src/main/java/net/ipip/ipdb/InvalidDatabaseException.java @@ -9,4 +9,8 @@ public class InvalidDatabaseException extends IOException { public InvalidDatabaseException(String message) { super(message); } + + public InvalidDatabaseException(Throwable cause) { + super(cause); + } } \ No newline at end of file diff --git a/src/main/java/net/ipip/ipdb/RandomAccessReader.java b/src/main/java/net/ipip/ipdb/RandomAccessReader.java new file mode 100644 index 0000000..f8e3aad --- /dev/null +++ b/src/main/java/net/ipip/ipdb/RandomAccessReader.java @@ -0,0 +1,127 @@ +package net.ipip.ipdb; + +import java.io.*; +import java.lang.reflect.Field; +import java.nio.charset.StandardCharsets; + +import com.alibaba.fastjson.JSONObject; + +import sun.misc.Unsafe; + +/** + * @author mckuhei + * */ +public class RandomAccessReader extends Reader implements Closeable { + + private static final Unsafe U = getUnsafe(); + private RandomAccessFile handle; + private long baseOffset; + + public RandomAccessReader(String file) throws IOException, InvalidDatabaseException { + this(new File(file)); + } + + public RandomAccessReader(File file) throws IOException, InvalidDatabaseException { + try { + this.fileSize = (int) file.length(); + RandomAccessFile handle = this.handle = new RandomAccessFile(file, "r"); + int metaLength = handle.readInt(); + byte[] metaBytes = new byte[metaLength]; + handle.readFully(metaBytes); + + String metaString = new String(metaBytes); + + MetaData meta = JSONObject.parseObject(metaString, MetaData.class); + this.nodeCount = meta.nodeCount; + this.meta = meta; + + this.baseOffset = handle.getFilePointer(); + + /** for ipv4 */ + if (0x01 == (this.meta.IPVersion & 0x01)) + { + int node = 0; + for (int i = 0; i < 96 && node < this.nodeCount; i++) { + if (i >= 80) { + node = this.readNode(node, 1); + } else { + node = this.readNode(node, 0); + } + } + + this.v4offset = node; + } + } catch (FileNotFoundException e) { + throw e; + } catch (IOException e) { + throw new InvalidDatabaseException(e); + } + } + + @Override + int readNode(int node, int index) { + int off = node * 8 + index * 4; + RandomAccessFile handle = this.handle; + try { + synchronized (handle) { + handle.seek(this.baseOffset + off); + return handle.readInt(); + } + } catch (IOException e) { + U.throwException(e); + } + throw new InternalError("Should not reach here"); + } + + @Override + String resolve(int node) throws InvalidDatabaseException { + final int resoloved = node - this.nodeCount + this.nodeCount * 8; + if (resoloved >= this.fileSize) { + throw new InvalidDatabaseException("database resolve error"); + } + + RandomAccessFile handle = this.handle; + + try { + byte[] data; + synchronized (handle) { + handle.seek(this.baseOffset + resoloved); + int size = handle.readUnsignedShort(); + + if (this.fileSize < (resoloved + 2 + size)) { + throw new InvalidDatabaseException("database resolve error"); + } + + data = new byte[size]; + handle.readFully(data); + } + return new String(data, StandardCharsets.UTF_8); + } catch (IOException e) { + U.throwException(e); + } + throw new InternalError("Should not reach here"); + } + + private static Unsafe getUnsafe() { + try { + Field f = Unsafe.class.getDeclaredField("theUnsafe"); + f.setAccessible(true); + return (Unsafe) f.get(null); + } catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) { + throw new InternalError("Unable to get unsafe", e); + } + } + + @Override + protected void finalize() { + close(); + } + + public void close() { + try { + this.handle.close(); + } catch (IOException e) { + U.throwException(e); + } + } +} diff --git a/src/main/java/net/ipip/ipdb/Reader.java b/src/main/java/net/ipip/ipdb/Reader.java index 5c9e80c..abcd438 100644 --- a/src/main/java/net/ipip/ipdb/Reader.java +++ b/src/main/java/net/ipip/ipdb/Reader.java @@ -1,32 +1,38 @@ package net.ipip.ipdb; -import com.alibaba.fastjson.JSONObject; -import sun.net.util.IPAddressUtil; - import java.io.*; +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; import java.util.Arrays; +import com.alibaba.fastjson.JSONObject; public class Reader { - private int fileSize; - private int nodeCount; + protected int fileSize; + protected int nodeCount; - private MetaData meta; - private byte[] data; + protected MetaData meta; + protected byte[] data; - private int v4offset; + protected int v4offset; + + Reader() { + + } public Reader(String name) throws IOException, InvalidDatabaseException { - this(new FileInputStream(new File(name))); + File file = new File(name); + this.init(this.readAllAsStream(new FileInputStream(file), (int) file.length())); } public Reader(InputStream in) throws IOException, InvalidDatabaseException { - this.init(this.readAllAsStream(in)); + this.init(this.readAllAsStream(in, 32)); } - protected byte[] readAllAsStream(InputStream in) throws IOException { - ByteArrayOutputStream out = new ByteArrayOutputStream(); + protected byte[] readAllAsStream(InputStream in, int initalSize) throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(initalSize); byte[] buffer = new byte[4096]; int n; while ((n = in.read(buffer)) != -1) { @@ -62,7 +68,7 @@ protected void init(byte[] data) throws InvalidDatabaseException { throw new InvalidDatabaseException(e.getMessage()); } - if ((meta.totalSize + Long.valueOf(metaLength).intValue() + 4) != this.data.length) { + if ((this.meta.totalSize + Long.valueOf(metaLength).intValue() + 4) != this.data.length) { throw new InvalidDatabaseException("database file size error"); } @@ -96,7 +102,7 @@ public String[] find(String addr, String language) throws IPFormatException, Inv byte[] ipv; if (addr.indexOf(":") >= 0) { - ipv = IPAddressUtil.textToNumericFormatV6(addr); + ipv = textToNumericFormat(addr); if (ipv == null) { throw new IPFormatException("ipv6 format error"); } @@ -105,7 +111,7 @@ public String[] find(String addr, String language) throws IPFormatException, Inv } } else if (addr.indexOf(".") > 0) { - ipv = IPAddressUtil.textToNumericFormatV4(addr); + ipv = textToNumericFormat(addr); if (ipv == null) { throw new IPFormatException("ipv4 format error"); } @@ -128,11 +134,19 @@ public String[] find(String addr, String language) throws IPFormatException, Inv return Arrays.copyOfRange(data.split("\t", this.meta.Fields.length * this.meta.Languages.size()), off, off+this.meta.Fields.length); } - private int findNode(byte[] binary) throws NotFoundException { + byte[] textToNumericFormat(String addr) throws IPFormatException { + try { + return InetAddress.getByName(addr).getAddress(); + } catch (UnknownHostException e) { + throw new IPFormatException(e); + } + } + + int findNode(byte[] binary) throws NotFoundException { int node = 0; - final int bit = binary.length * 8; + final int bit = binary.length << 3; if (bit == 32) { node = this.v4offset; @@ -143,7 +157,7 @@ private int findNode(byte[] binary) throws NotFoundException { break; } - node = this.readNode(node, 1 & ((0xFF & binary[i / 8]) >> 7 - (i % 8))); + node = this.readNode(node, 1 & ((0xFF & binary[i >> 3]) >> 7 - (i & 7))); } if (node > this.nodeCount) { @@ -153,7 +167,7 @@ private int findNode(byte[] binary) throws NotFoundException { throw new NotFoundException("ip not found"); } - private String resolve(int node) throws InvalidDatabaseException { + String resolve(int node) throws InvalidDatabaseException { final int resoloved = node - this.nodeCount + this.nodeCount * 8; if (resoloved >= this.fileSize) { throw new InvalidDatabaseException("database resolve error"); @@ -167,18 +181,14 @@ private String resolve(int node) throws InvalidDatabaseException { this.data[resoloved+1] )).intValue(); - if (this.data.length < (resoloved + 2 + size)) { + if (this.fileSize < (resoloved + 2 + size)) { throw new InvalidDatabaseException("database resolve error"); } - try { - return new String(this.data, resoloved + 2, size, "UTF-8"); - } catch (UnsupportedEncodingException e) { - throw new InvalidDatabaseException("database resolve error"); - } + return new String(this.data, resoloved + 2, size, StandardCharsets.UTF_8); } - private int readNode(int node, int index) { + int readNode(int node, int index) { int off = node * 8 + index * 4; return Long.valueOf(bytesToLong( @@ -194,11 +204,7 @@ private static long bytesToLong(byte a, byte b, byte c, byte d) { } private static long int2long(int i) { - long l = i & 0x7fffffffL; - if (i < 0) { - l |= 0x080000000L; - } - return l; + return i & 0xFFFFFFFFL; } public boolean isIPv4() { diff --git a/src/main/java/net/ipip/ipdb/Risk.java b/src/main/java/net/ipip/ipdb/Risk.java index db234b1..a914c14 100644 --- a/src/main/java/net/ipip/ipdb/Risk.java +++ b/src/main/java/net/ipip/ipdb/Risk.java @@ -1,5 +1,6 @@ package net.ipip.ipdb; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.util.Arrays; @@ -14,7 +15,7 @@ public class Risk { private Reader reader; public Risk(String name) throws IOException,InvalidDatabaseException { - this.reader = new Reader(name); + this.reader = new RandomAccessReader(name); } public Risk(InputStream in) throws IOException, InvalidDatabaseException { @@ -23,8 +24,11 @@ public Risk(InputStream in) throws IOException, InvalidDatabaseException { public boolean reload(String name) { try { - Reader r = new Reader(name); - this.reader = r; + Reader old = this.reader; + this.reader = new RandomAccessReader(name); + if (old instanceof Closeable) { + ((Closeable) old).close(); + } } catch (Exception e) { return false; }