diff --git a/README.md b/README.md index ff1c9ff87..0dce6d353 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ [![Android CI](https://github.com/AngelAuraMC/Amethyst-Android/workflows/Android%20CI/badge.svg)](https://github.com/AngelAuraMC/Amethyst-Android/actions) [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/AngelAuraMC/Amethyst-Android)](https://github.com/AngelAuraMC/Amethyst-Android/actions) -[![Crowdin](https://badges.crowdin.net/amethyst/localized.svg)](https://crowdin.com/project/amethyst) +[![Crowdin](https://badges.crowdin.net/pojavlauncher/localized.svg)](https://crowdin.com/project/pojavlauncher) [![Discord](https://img.shields.io/discord/724163890803638273.svg?label=&logo=discord&logoColor=ffffff&color=7389D8&labelColor=6A7EC2)](https://discord.gg/5ptqkyZxEy) *From [Boardwalk](https://github.com/zhuowei/Boardwalk)'s ashes and [PojavLauncher](https://github.com/PojavLauncherTeam/PojavLauncher)'s ruined reputation, here comes Amethyst!* diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar index a7565f7d7..1886d0f79 100644 Binary files a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar and b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/arc_dns_injector.jar differ diff --git a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version index 9ce6c430b..f32989695 100644 --- a/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version +++ b/app_pojavlauncher/src/main/assets/components/arc_dns_injector/version @@ -1 +1 @@ -53f2f037cef9b7447a6abbdf82150ca8a2f2a587 \ No newline at end of file +2016eba00f043842122d7aecb9410cf9371a7693 \ No newline at end of file diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java index 1260cdde6..7b2952d3b 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ModVersionListFragment.java @@ -17,7 +17,6 @@ import androidx.fragment.app.Fragment; import net.kdt.pojavlaunch.R; import net.kdt.pojavlaunch.Tools; import net.kdt.pojavlaunch.extra.ExtraCore; -import net.kdt.pojavlaunch.mirrors.DownloadMirror; import net.kdt.pojavlaunch.modloaders.ModloaderDownloadListener; import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; @@ -106,7 +105,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Object forgeVersion = expandableListView.getExpandableListAdapter().getChild(i, i1); ModloaderListenerProxy taskProxy = new ModloaderListenerProxy(); Runnable downloadTask = createDownloadTask(forgeVersion, taskProxy); - setTaskProxy(taskProxy); + setTaskProxyValue(taskProxy); taskProxy.attachListener(this); mExpandableListView.setEnabled(false); new Thread(downloadTask).start(); @@ -118,7 +117,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); // Read the comment in FabricInstallFragment.onDownloadFinished() to see how this works getParentFragmentManager().popBackStackImmediate(); @@ -131,7 +130,7 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); Tools.dialog(context, context.getString(R.string.global_error), @@ -144,15 +143,18 @@ public abstract class ModVersionListFragment extends Fragment implements Runn Tools.runOnUiThread(()->{ Context context = requireContext(); getTaskProxy().detachListener(); - setTaskProxy(null); + deleteTaskProxy(); mExpandableListView.setEnabled(true); Tools.showError(context, e); }); } - private void setTaskProxy(ModloaderListenerProxy proxy) { + private void setTaskProxyValue(ModloaderListenerProxy proxy) { ExtraCore.setValue(mExtraTag, proxy); } + private void deleteTaskProxy(){ + ExtraCore.removeValue(mExtraTag); + } private ModloaderListenerProxy getTaskProxy() { return (ModloaderListenerProxy) ExtraCore.getValue(mExtraTag); diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java new file mode 100644 index 000000000..b901da6e8 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/NeoForgeInstallFragment.java @@ -0,0 +1,80 @@ +package net.kdt.pojavlaunch.fragments; + +import android.content.Context; +import android.content.Intent; +import android.view.LayoutInflater; +import android.widget.ExpandableListAdapter; + +import androidx.annotation.NonNull; + +import net.kdt.pojavlaunch.JavaGUILauncherActivity; +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.modloaders.ForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.ForgeUtils; +import net.kdt.pojavlaunch.modloaders.ModloaderListenerProxy; +import net.kdt.pojavlaunch.modloaders.NeoForgeDownloadTask; +import net.kdt.pojavlaunch.modloaders.NeoForgeVersionListAdapter; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.List; + +public class NeoForgeInstallFragment extends ModVersionListFragment> { + public static final String TAG = "NeoForgeInstallFragment"; + public NeoForgeInstallFragment() { + super(TAG); + } + + private static final String NEOFORGE_METADATA_URL = "https://meta.prismlauncher.org/v1/net.neoforged/index.json"; + + + @Override + public void onAttach(@NonNull Context context) { + super.onAttach(context); + } + + @Override + public int getTitleText() { + return R.string.neoforge_dl_select_version; + } + + @Override + public int getNoDataMsg() { + return R.string.neoforge_dl_no_installer; + } + + @Override + public List loadVersionList() { + String test = null; + try { + test = DownloadUtils.downloadStringCached(NEOFORGE_METADATA_URL, "neoforge_versions", input -> input); + } catch (Exception e) { + Tools.showErrorRemote(e); + } + return Collections.singletonList(test); + // Moved the parsing logic to the adapter because there is no way to get this info easily, we use prism's index + // since neoforge doesn't actually give this information easily anywhere. + // To clarify, neoforge does not provide maven APIs to get supported Minecraft versions for each loader version + + } + + @Override + public ExpandableListAdapter createAdapter(List versionList, LayoutInflater layoutInflater) { + return new NeoForgeVersionListAdapter(versionList, layoutInflater); + } + + @Override + public Runnable createDownloadTask(Object selectedVersion, ModloaderListenerProxy listenerProxy) { + return new NeoForgeDownloadTask(listenerProxy, (String) selectedVersion); + } + + @Override + public void onDownloadFinished(Context context, File downloadedFile) { + Intent modInstallerStartIntent = new Intent(context, JavaGUILauncherActivity.class); + modInstallerStartIntent.putExtra("javaArgs", "-jar "+downloadedFile.getAbsolutePath()+" --install-client"); + context.startActivity(modInstallerStartIntent); + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java index 9a82ccb9f..9acb9d15a 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/fragments/ProfileTypeSelectFragment.java @@ -36,6 +36,8 @@ public class ProfileTypeSelectFragment extends Fragment { tryInstall(FabricInstallFragment.class, FabricInstallFragment.TAG)); view.findViewById(R.id.modded_profile_forge).setOnClickListener((v)-> tryInstall(ForgeInstallFragment.class, ForgeInstallFragment.TAG)); + view.findViewById(R.id.modded_profile_neoforge).setOnClickListener((v)-> + tryInstall(NeoForgeInstallFragment.class, NeoForgeInstallFragment.TAG)); view.findViewById(R.id.modded_profile_modpack).setOnClickListener((v)-> tryInstall(SearchModFragment.class, SearchModFragment.TAG)); view.findViewById(R.id.modded_profile_quilt).setOnClickListener((v)-> diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java new file mode 100644 index 000000000..9cb40e683 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeDownloadTask.java @@ -0,0 +1,88 @@ +package net.kdt.pojavlaunch.modloaders; + +import com.kdt.mcgui.ProgressLayout; + +import net.kdt.pojavlaunch.R; +import net.kdt.pojavlaunch.Tools; +import net.kdt.pojavlaunch.progresskeeper.ProgressKeeper; +import net.kdt.pojavlaunch.utils.DownloadUtils; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.List; + +public class NeoForgeDownloadTask implements Runnable, Tools.DownloaderFeedback { + private String mDownloadUrl; + private String mFullVersion; + private String mLoaderVersion; + private String mGameVersion; + private final ModloaderDownloadListener mListener; + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String forgeVersion) { + this.mListener = listener; + this.mDownloadUrl = "https://maven.neoforged.net/releases/net/neoforged/neoforge/"+ forgeVersion +"/neoforge-"+forgeVersion+"-installer.jar"; + this.mFullVersion = forgeVersion; + } + + public NeoForgeDownloadTask(ModloaderDownloadListener listener, String gameVersion, String loaderVersion) { + this.mListener = listener; + this.mLoaderVersion = loaderVersion; + this.mGameVersion = gameVersion; + } + @Override + public void run() { + if(determineDownloadUrl()) { + downloadForge(); + } + ProgressLayout.clearProgress(ProgressLayout.INSTALL_MODPACK); + } + + @Override + public void updateProgress(int curr, int max) { + int progress100 = (int)(((float)curr / (float)max)*100f); + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, progress100, R.string.forge_dl_progress, mFullVersion); + } + + private void downloadForge() { + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_progress, mFullVersion); + try { + File destinationFile = new File(Tools.DIR_CACHE, "neoforge-installer.jar"); + byte[] buffer = new byte[8192]; + DownloadUtils.downloadFileMonitored(mDownloadUrl, destinationFile, buffer, this); + mListener.onDownloadFinished(destinationFile); + }catch (FileNotFoundException e) { + mListener.onDataNotAvailable(); + } catch (IOException e) { + mListener.onDownloadError(e); + } + } + + public boolean determineDownloadUrl() { + if(mDownloadUrl != null && mFullVersion != null) return true; + ProgressKeeper.submitProgress(ProgressLayout.INSTALL_MODPACK, 0, R.string.forge_dl_searching); + try { + if(!findVersion()) { + mListener.onDataNotAvailable(); + return false; + } + }catch (IOException e) { + mListener.onDownloadError(e); + return false; + } + return true; + } + + public boolean findVersion() throws IOException { + List forgeVersions = ForgeUtils.downloadForgeVersions(); + if(forgeVersions == null) return false; + String versionStart = mGameVersion+"-"+mLoaderVersion; + for(String versionName : forgeVersions) { + if(!versionName.startsWith(versionStart)) continue; + mFullVersion = versionName; + mDownloadUrl = ForgeUtils.getInstallerUrl(mFullVersion); + return true; + } + return false; + } + +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java new file mode 100644 index 000000000..e02670329 --- /dev/null +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/modloaders/NeoForgeVersionListAdapter.java @@ -0,0 +1,119 @@ +package net.kdt.pojavlaunch.modloaders; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.BaseExpandableListAdapter; +import android.widget.ExpandableListAdapter; +import android.widget.TextView; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; + +public class NeoForgeVersionListAdapter extends BaseExpandableListAdapter implements ExpandableListAdapter { + private final LayoutInflater mLayoutInflater; + private final LinkedHashMap> minecraftToLoaderVersionsHashmap; + private LinkedHashSet generatedHashSet = null; + + + public NeoForgeVersionListAdapter(List forgeVersions, LayoutInflater layoutInflater) { + this.mLayoutInflater = layoutInflater; + minecraftToLoaderVersionsHashmap = new LinkedHashMap<>(); + JsonArray versionsJsonArray = JsonParser.parseString(forgeVersions.get(0)).getAsJsonObject().getAsJsonArray("versions"); + + ArrayList sortedVersionsList = new ArrayList<>(); + for (JsonElement elem : versionsJsonArray) { + sortedVersionsList.add(elem); + } + Collections.sort(sortedVersionsList, (o1, o2) -> { + String versionString1 = ((JsonObject) o1).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + String versionString2 = ((JsonObject) o2).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + return versionString2.compareTo(versionString1); // Sorts by Minecraft version + }); + + for (JsonElement sortedVersionPick : sortedVersionsList) { + String loaderVersion = ((JsonObject) sortedVersionPick).get("version").getAsString(); + String minecraftVersion = ((JsonObject) sortedVersionPick).get("requires").getAsJsonArray().get(0).getAsJsonObject().get("equals").getAsString(); + if (minecraftToLoaderVersionsHashmap.containsKey(minecraftVersion)) { + minecraftToLoaderVersionsHashmap.get(minecraftVersion).add(loaderVersion); + } else { + generatedHashSet = new LinkedHashSet<>(); + generatedHashSet.add(loaderVersion); + minecraftToLoaderVersionsHashmap.put(minecraftVersion, generatedHashSet); + } + } + } + @Override + public int getGroupCount() { + return minecraftToLoaderVersionsHashmap.size(); + } + + @Override + public int getChildrenCount(int i) { + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).size(); + } + + @Override + public Object getGroup(int i) { + return getGameVersion(i); + } + + @Override + public Object getChild(int i, int i1) { + return getForgeVersion(i, i1); + } + + @Override + public long getGroupId(int i) { + return i; + } + + @Override + public long getChildId(int i, int i1) { + return i1; + } + + @Override + public boolean hasStableIds() { + return true; + } + + @Override + public View getGroupView(int i, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + + ((TextView) convertView).setText(getGameVersion(i)); + + return convertView; + } + + @Override + public View getChildView(int i, int i1, boolean b, View convertView, ViewGroup viewGroup) { + if(convertView == null) + convertView = mLayoutInflater.inflate(android.R.layout.simple_expandable_list_item_1, viewGroup, false); + ((TextView) convertView).setText(getForgeVersion(i, i1)); + return convertView; + } + + private String getGameVersion(int i) { + return minecraftToLoaderVersionsHashmap.keySet().toArray()[i].toString(); + } + + private String getForgeVersion(int i, int i1){ + return new ArrayList<>(minecraftToLoaderVersionsHashmap.values()).get(i).toArray()[i1].toString(); + } + + @Override + public boolean isChildSelectable(int i, int i1) { + return true; + } +} diff --git a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java index 0a18ac0fb..3238a4ffd 100644 --- a/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java +++ b/app_pojavlauncher/src/main/java/net/kdt/pojavlaunch/tasks/AsyncVersionList.java @@ -26,7 +26,7 @@ public class AsyncVersionList { public void getVersionList(@Nullable VersionDoneListener listener, boolean secondPass){ sExecutorService.execute(() -> { - File versionFile = new File(Tools.DIR_DATA + "/version_list.json"); + File versionFile = new File(Tools.DIR_CACHE + "/version_list.json"); JMinecraftVersionList versionList = null; try{ if(!versionFile.exists() || (System.currentTimeMillis() > versionFile.lastModified() + 86400000 )){ @@ -68,7 +68,7 @@ public class AsyncVersionList { // Then save the version list //TODO make it not save at times ? - FileOutputStream fos = new FileOutputStream(Tools.DIR_DATA + "/version_list.json"); + FileOutputStream fos = new FileOutputStream(Tools.DIR_CACHE + "/version_list.json"); fos.write(jsonString.getBytes()); fos.close(); diff --git a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml index 5ada83d77..5ce18cd93 100644 --- a/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml +++ b/app_pojavlauncher/src/main/res/layout/fragment_profile_type.xml @@ -103,6 +103,14 @@ android:layout_marginTop="@dimen/padding_large" android:text="@string/modloader_dl_install_forge" /> + + Delete account and log in Please log in again Selected account is corrupted + Create Neoforge profile + Select NeoForge version + Sorry, but this version of NeoForge does not have an installer, which is not yet supported. diff --git a/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java b/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java index f21479feb..02d6249c2 100644 --- a/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java +++ b/arc_dns_injector/src/main/java/git/artdeell/arcdns/ArcDNSInjectorAgent.java @@ -5,16 +5,23 @@ public class ArcDNSInjectorAgent { System.out.println("Arc Capes DNS Injector"); System.out.println("Parts of Alibaba's DCM library were used, please read https://github.com/alibaba/java-dns-cache-manipulator/blob/main/README.md for more info"); String[] injectedIps = new String[]{args}; + try { - CacheUtil_J9.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + CacheUtil_J21.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); } catch (Exception e) { try { - CacheUtil_J8.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + CacheUtil_J9.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); } catch (Exception e2) { - System.out.println("Failed to inject cache!"); - e2.addSuppressed(e); - e2.printStackTrace(); - return; + try { + CacheUtil_J8.setInetAddressCache("s.optifine.net", injectedIps, CacheUtilCommons.NEVER_EXPIRATION); + } catch (Exception e3) { + System.out.println("Failed to inject cache!"); + e2.addSuppressed(e); + e2.printStackTrace(); + e3.addSuppressed(e); + e3.printStackTrace(); + return; + } } } System.out.println("Added DNS cache entry: s.optifine.net/"+args); diff --git a/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java b/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java new file mode 100644 index 000000000..8e2501a7a --- /dev/null +++ b/arc_dns_injector/src/main/java/git/artdeell/arcdns/CacheUtil_J21.java @@ -0,0 +1,145 @@ +package git.artdeell.arcdns; +import static git.artdeell.arcdns.CacheUtilCommons.NEVER_EXPIRATION; + +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.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ConcurrentSkipListSet; +public class CacheUtil_J21 { + public static void setInetAddressCache(String host, String[] ips, long expireMillis) + throws UnknownHostException, IllegalAccessException, InstantiationException, + InvocationTargetException, ClassNotFoundException, NoSuchFieldException { + long expiration = expireMillis == NEVER_EXPIRATION ? NEVER_EXPIRATION : System.nanoTime() + expireMillis * 1_000_000; + Object cachedAddresses = newCachedAddresses(host, ips, expiration); + + getCacheOfInetAddress().put(host, cachedAddresses); + getExpirySetOfInetAddress().add(cachedAddresses); + } + + private static Object newCachedAddresses(String host, String[] ips, long expiration) + throws ClassNotFoundException, UnknownHostException, IllegalAccessException, + InvocationTargetException, InstantiationException { + // InetAddress.CachedAddresses has only one constructor + return getConstructorOfInetAddress$CachedAddresses().newInstance(host, CacheUtilCommons.toInetAddressArray(host, ips), expiration); + } + + private static volatile Constructor constructorOfInetAddress$CachedAddresses = null; + + private static Constructor getConstructorOfInetAddress$CachedAddresses() throws ClassNotFoundException { + if (constructorOfInetAddress$CachedAddresses != null) return constructorOfInetAddress$CachedAddresses; + + synchronized (CacheUtilCommons.class) { + // double check + if (constructorOfInetAddress$CachedAddresses != null) return constructorOfInetAddress$CachedAddresses; + + final Class clazz = Class.forName(inetAddress$CachedAddresses_ClassName); + + // InetAddress.CacheEntry has only one constructor: + // + // - for jdk 9-jdk12, constructor signature is CachedAddresses(String host, InetAddress[] inetAddresses, long expiryTime) + // code in jdk 9: + // https://hg.openjdk.java.net/jdk9/jdk9/jdk/file/65464a307408/src/java.base/share/classes/java/net/InetAddress.java#l783 + // code in jdk 11: + // https://hg.openjdk.java.net/jdk/jdk11/file/1ddf9a99e4ad/src/java.base/share/classes/java/net/InetAddress.java#l787 + final Constructor constructor = clazz.getDeclaredConstructors()[0]; + constructor.setAccessible(true); + + constructorOfInetAddress$CachedAddresses = constructor; + return constructor; + } + } + + public static void removeInetAddressCache(String host) throws NoSuchFieldException, IllegalAccessException { + getCacheOfInetAddress().remove(host); + removeHostFromExpirySetOfInetAddress(host); + } + + /** + * @see #getExpirySetOfInetAddress() + */ + private static void removeHostFromExpirySetOfInetAddress(String host) + throws NoSuchFieldException, IllegalAccessException { + for (Iterator iterator = getExpirySetOfInetAddress().iterator(); iterator.hasNext(); ) { + Object cachedAddresses = iterator.next(); + if (getHostOfInetAddress$CacheAddress(cachedAddresses).equals(host)) { + iterator.remove(); + } + } + } + + private static volatile Field hostFieldOfInetAddress$CacheAddress = null; + + private static String getHostOfInetAddress$CacheAddress(Object cachedAddresses) + throws NoSuchFieldException, IllegalAccessException { + if (hostFieldOfInetAddress$CacheAddress == null) { + synchronized (CacheUtil_J21.class) { + if (hostFieldOfInetAddress$CacheAddress == null) { // double check + final Field f = cachedAddresses.getClass().getDeclaredField("host"); + f.setAccessible(true); + hostFieldOfInetAddress$CacheAddress = f; + } + } + } + return (String) hostFieldOfInetAddress$CacheAddress.get(cachedAddresses); + } + + + ////////////////////////////////////////////////////////////////////////////// + // getters of static cache related fields of InetAddress + ////////////////////////////////////////////////////////////////////////////// + + @SuppressWarnings("unchecked") + private static ConcurrentMap getCacheOfInetAddress() + throws NoSuchFieldException, IllegalAccessException { + return (ConcurrentMap) getCacheAndExpirySetOfInetAddress0()[0]; + } + + @SuppressWarnings("unchecked") + private static ConcurrentSkipListSet getExpirySetOfInetAddress() + throws NoSuchFieldException, IllegalAccessException { + return (ConcurrentSkipListSet) getCacheAndExpirySetOfInetAddress0()[1]; + } + + private static volatile Object[] ADDRESS_CACHE_AND_EXPIRY_SET = null; + + private static Object[] getCacheAndExpirySetOfInetAddress0() + throws NoSuchFieldException, IllegalAccessException { + if (ADDRESS_CACHE_AND_EXPIRY_SET != null) return ADDRESS_CACHE_AND_EXPIRY_SET; + + synchronized (CacheUtil_J21.class) { + if (ADDRESS_CACHE_AND_EXPIRY_SET != null) return ADDRESS_CACHE_AND_EXPIRY_SET; + + final Field cacheField = InetAddress.class.getDeclaredField("cache"); + cacheField.setAccessible(true); + + final Field expirySetField = InetAddress.class.getDeclaredField("expirySet"); + expirySetField.setAccessible(true); + + ADDRESS_CACHE_AND_EXPIRY_SET = new Object[]{ + cacheField.get(InetAddress.class), + expirySetField.get(InetAddress.class) + }; + + return ADDRESS_CACHE_AND_EXPIRY_SET; + } + } + + ////////////////////////////////////////////////////////////////////////////// + + private static final String inetAddress$CachedAddresses_ClassName = "java.net.InetAddress$CachedLookup"; + public static void clearInetAddressCache() throws NoSuchFieldException, IllegalAccessException { + getCacheOfInetAddress().clear(); + getExpirySetOfInetAddress().clear(); + } + + private CacheUtil_J21() { + } + +}