package com.oldratlee.vdns.internal;

import com.oldratlee.vdns.DnsCacheEntry;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Pattern;

/**
 * @author ding.lid
 * @see InetAddress
 */
public class InetAddressCacheUtil {

    public static void setInetAddressCache(String host, String[] ips, long expiration)
            throws NoSuchMethodException, UnknownHostException,
            IllegalAccessException, InstantiationException, InvocationTargetException,
            ClassNotFoundException, NoSuchFieldException {
        Object entry = createCacheEntry(host, ips, expiration);
        synchronized (getAddressCacheFieldOfInetAddress()) {
            getCacheFiledOfInetAddress$CacheEntry().put(host, entry);
        }
    }

    static Object createCacheEntry(String host, String[] ips, long expiration)
            throws UnknownHostException, ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InvocationTargetException, InstantiationException {
        String className = "java.net.InetAddress$CacheEntry";
        Class<?> clazz = Class.forName(className);
        Constructor<?> constructor = clazz.getDeclaredConstructor(InetAddress[].class, long.class);
        constructor.setAccessible(true);
        return constructor.newInstance(toInetAddressArray(host, ips), expiration);
    }

    @SuppressWarnings("unchecked")
    static Map<String, Object> getCacheFiledOfInetAddress$CacheEntry()
            throws NoSuchFieldException, IllegalAccessException {
        final Object inetAddressCache = getAddressCacheFieldOfInetAddress();
        Class clazz = inetAddressCache.getClass();

        final Field cacheMapField = clazz.getDeclaredField("cache");
        cacheMapField.setAccessible(true);
        return (Map<String, Object>) cacheMapField.get(inetAddressCache);
    }

    static volatile Object ADDRESS_CACHE = null;

    @SuppressWarnings("unchecked")
    static Object getAddressCacheFieldOfInetAddress()
            throws NoSuchFieldException, IllegalAccessException {
        if (ADDRESS_CACHE == null) {
            synchronized (InetAddressCacheUtil.class) {
                final Field cacheField = InetAddress.class.getDeclaredField("addressCache");
                cacheField.setAccessible(true);
                ADDRESS_CACHE = cacheField.get(InetAddress.class);
            }
        }
        return ADDRESS_CACHE;
    }

    static InetAddress[] toInetAddressArray(String host, String[] ips) throws UnknownHostException {
        InetAddress[] addresses = new InetAddress[ips.length];
        for (int i = 0; i < addresses.length; i++) {
            addresses[i] = InetAddress.getByAddress(host, ip2ByteArray(ips[i]));
        }

        return addresses;
    }

    private static Pattern PATTERN_DOT = Pattern.compile("\\.");

    static byte[] ip2ByteArray(String ip) {
        final String[] ipParts = PATTERN_DOT.split(ip); // FIXME check ip validation

        byte[] address = new byte[ipParts.length];
        for (int i = 0; i < ipParts.length; i++) {
            address[i] = (byte) Integer.parseInt(ipParts[i]);
        }
        return address;
    }

    public static List<DnsCacheEntry> listAllVirtualDns()
            throws NoSuchFieldException, IllegalAccessException {
        List<DnsCacheEntry> list = new ArrayList<DnsCacheEntry>();

        final Map<String, Object> cache;
        synchronized (getAddressCacheFieldOfInetAddress()) {
            cache = new HashMap<String, Object>(getCacheFiledOfInetAddress$CacheEntry());
        }

        for (Map.Entry<String, Object> entry : cache.entrySet()) {
            list.addAll(inetAddress$CacheEntry2DnsCacheEntry(entry.getValue()));
        }
        return list;
    }

    static List<DnsCacheEntry> inetAddress$CacheEntry2DnsCacheEntry(Object entry)
            throws NoSuchFieldException, IllegalAccessException {
        Class<?> cacheEntryClazz = entry.getClass();

        Field expirationField = cacheEntryClazz.getDeclaredField("expiration");
        expirationField.setAccessible(true);
        long expiration = (Long) expirationField.get(entry);

        Field addressesField = cacheEntryClazz.getDeclaredField("addresses");
        addressesField.setAccessible(true);
        InetAddress[] addresses = (InetAddress[]) addressesField.get(entry);

        List<DnsCacheEntry> dnsCacheEntries = new ArrayList<DnsCacheEntry>();
        for (InetAddress address : addresses) {
            DnsCacheEntry dnsCacheEntry = new DnsCacheEntry(address.getHostName(),
                    address.getHostAddress(), new Date(expiration));
            dnsCacheEntries.add(dnsCacheEntry);
        }
        return dnsCacheEntries;
    }
}

@@ -421,51 +421,698 @@
 n;%0A%0A
-/**%0A * @author ding.lid%0A * @see InetAddress
+import javax.annotation.concurrent.GuardedBy;%0A%0A/**%0A * Util class to manipulate dns cache %7B@link InetAddress.Cache#cache%7D at %7B@link InetAddress#addressCache%7D.%0A * %3Cp/%3E%0A * %3Cb%3ECaution%3C/b%3E: %3Cbr%3E%0A * Manipulation on %7B@link InetAddress#addressCache%7D %3Cstrong%3EMUST%3C/strong%3E%0A * be guarded by %7B@link InetAddress#addressCache%7D to avoid multithreaded problem,%0A * you can see the implementation of %7B@link InetAddress%7D to confirm this%0A * (%3Cb%3E%3Ci%3ESee Also%3C/i%3E%3C/b%3E lists key code of %7B@link InetAddress%7D related to this point).%0A *%0A * @author ding.lid%0A * @see InetAddress%0A * @see InetAddress#addressCache%0A * @see InetAddress#cacheInitIfNeeded()%0A * @see InetAddress#cacheAddresses(String, InetAddress%5B%5D, boolean)
 %0A */
@@ -1148,17 +1148,16 @@
 eUtil %7B%0A
-%0A
     publ
@@ -2283,32 +2283,86 @@
 gs(%22unchecked%22)%0A
+    @GuardedBy(%22getAddressCacheFieldOfInetAddress()%22)%0A
     static Map%3CS
@@ -2858,43 +2858,8 @@
 l;%0A%0A
-    @SuppressWarnings(%22unchecked%22)%0A
     
@@ -4838,18 +4838,18 @@
 EntryCla
-zz
+ss
  = entry
@@ -4899,34 +4899,34 @@
  = cacheEntryCla
-zz
+ss
 .getDeclaredFiel
@@ -5093,18 +5093,18 @@
 EntryCla
-zz
+ss
 .getDecl

add concurrent annoation and javadoc #2