├── src └── main │ └── java │ └── com │ └── maxiaofa │ └── hosts │ ├── async │ ├── worker │ │ ├── ResultState.java │ │ ├── DependWrapper.java │ │ └── WorkResult.java │ ├── exception │ │ └── SkippedException.java │ ├── callback │ │ ├── DefaultCallback.java │ │ ├── ITimeoutWorker.java │ │ ├── DefaultGroupCallback.java │ │ ├── ICallback.java │ │ ├── IGroupCallback.java │ │ └── IWorker.java │ ├── executor │ │ ├── timer │ │ │ └── SystemClock.java │ │ └── Async.java │ └── wrapper │ │ └── WorkerWrapper.java │ ├── constants │ ├── IpAddress.java │ └── GithubUrl.java │ ├── worker │ └── GetIpAddressWorker.java │ ├── config │ └── GenContentTheadPoolExecutorConfig.java │ ├── utils │ ├── FileUtils.java │ └── HtmlUtils.java │ └── RunHosts.java ├── .gitignore ├── README_TEMPLATE.md ├── .github └── workflows │ └── updateHosts.yml ├── hosts ├── pom.xml ├── README.md └── LICENSE /src/main/java/com/maxiaofa/hosts/async/worker/ResultState.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.worker; 2 | 3 | /** 4 | * 结果状态 5 | * @author wuweifeng wrote on 2019-11-19. 6 | */ 7 | public enum ResultState { 8 | SUCCESS, 9 | TIMEOUT, 10 | EXCEPTION, 11 | DEFAULT //默认状态 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/constants/IpAddress.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.constants; 2 | 3 | /** 4 | * @author MaXiaoFa 5 | */ 6 | public class IpAddress { 7 | 8 | public final static String SELECT_IP_ADDRESS_URL = "https://tools.tutorialspoint.com/ip_lookup_ajax.php?host="; 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/exception/SkippedException.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.exception; 2 | 3 | /** 4 | * 如果任务在执行之前,自己后面的任务已经执行完或正在被执行,则抛该exception 5 | * @author wuweifeng wrote on 2020-02-18 6 | * @version 1.0 7 | */ 8 | public class SkippedException extends RuntimeException { 9 | public SkippedException() { 10 | super(); 11 | } 12 | 13 | public SkippedException(String message) { 14 | super(message); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/DefaultCallback.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | 4 | import com.maxiaofa.hosts.async.worker.WorkResult; 5 | 6 | /** 7 | * 默认回调类,如果不设置的话,会默认给这个回调 8 | * @author wuweifeng wrote on 2019-11-19. 9 | */ 10 | public class DefaultCallback implements ICallback { 11 | @Override 12 | public void begin() { 13 | 14 | } 15 | 16 | @Override 17 | public void result(boolean success, T param, WorkResult workResult) { 18 | 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/ITimeoutWorker.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | /** 4 | * @author wuweifeng wrote on 2019-12-20 5 | * @version 1.0 6 | */ 7 | public interface ITimeoutWorker extends IWorker { 8 | /** 9 | * 每个worker都可以设置超时时间 10 | * @return 毫秒超时时间 11 | */ 12 | long timeOut(); 13 | 14 | /** 15 | * 是否开启单个执行单元的超时功能(有时是一个group设置个超时,而不具备关心单个worker的超时) 16 | *

注意,如果开启了单个执行单元的超时检测,将使线程池数量多出一倍

17 | * @return 是否开启 18 | */ 19 | boolean enableTimeOut(); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/DefaultGroupCallback.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | 4 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * @author wuweifeng wrote on 2019-12-27 10 | * @version 1.0 11 | */ 12 | public class DefaultGroupCallback implements IGroupCallback { 13 | @Override 14 | public void success(List workerWrappers) { 15 | 16 | } 17 | 18 | @Override 19 | public void failure(List workerWrappers, Exception e) { 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/ICallback.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | 4 | import com.maxiaofa.hosts.async.worker.WorkResult; 5 | 6 | /** 7 | * 每个执行单元执行完毕后,会回调该接口

8 | * 需要监听执行结果的,实现该接口即可 9 | * 10 | * @author wuweifeng wrote on 2019-11-19. 11 | */ 12 | @FunctionalInterface 13 | public interface ICallback { 14 | 15 | /** 16 | * 任务开始的监听 17 | */ 18 | default void begin() { 19 | 20 | } 21 | 22 | /** 23 | * 耗时操作执行完毕后,就给value注入值 24 | */ 25 | void result(boolean success, T param, WorkResult workResult); 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/IGroupCallback.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | 4 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 5 | 6 | import java.util.List; 7 | 8 | /** 9 | * 如果是异步执行整组的话,可以用这个组回调。不推荐使用 10 | * @author wuweifeng wrote on 2019-11-19. 11 | */ 12 | public interface IGroupCallback { 13 | /** 14 | * 成功后,可以从wrapper里去getWorkResult 15 | */ 16 | void success(List workerWrappers); 17 | /** 18 | * 失败了,也可以从wrapper里去getWorkResult 19 | */ 20 | void failure(List workerWrappers, Exception e); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ### STS ### 2 | .apt_generated 3 | .classpath 4 | .factorypath 5 | .project 6 | .settings 7 | .springBeans 8 | .sts4-cache 9 | bin/ 10 | !**/src/main/**/bin/ 11 | !**/src/test/**/bin/ 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | out/ 19 | !**/src/main/**/out/ 20 | !**/src/test/**/out/ 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | 29 | ### VS Code ### 30 | .vscode/ 31 | 32 | ### other ### 33 | HELP.md 34 | .gradle 35 | build/ 36 | !gradle/wrapper/gradle-wrapper.jar 37 | !**/src/main/**/build/ 38 | !**/src/test/**/build/ 39 | -------------------------------------------------------------------------------- /README_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # GitHub-Hosts 2 | 3 | ## 一、介绍 4 | 通过修改Hosts解决国内Github经常抽风访问不到 5 | 6 | --- 7 | 8 | ## 二、使用方法 9 | 10 | ### 2.1 复制下面的内容 11 | ```bash 12 | {[hosts]} 13 | ``` 14 | 最后更新时间:`{[update_time]}` 15 | 16 | ## 2.2 修改 hosts 文件 17 | hosts 文件在不同系统位置不一,详情如下: 18 | - Windows 系统:`C:\Windows\System32\drivers\etc\hosts`。 19 | - Mac(苹果电脑)系统:`/etc/hosts`。 20 | - Linux 系统:`/etc/hosts`。 21 | 22 | 修改方法,把2.1的内容复制到文本末尾: 23 | 24 | 1. Windows 使用记事本。 25 | 2. Linux、Mac 使用 Root 权限:`sudo vi /etc/hosts`。 26 | 27 | #### 2.3 激活生效 28 | 大部分情况下是直接生效,如未生效可尝试下面的办法,刷新 DNS: 29 | 30 | 1. Windows:在 CMD 窗口输入:`ipconfig /flushdns` 31 | 2. Mac 命令:`sudo killall -HUP mDNSResponder` 32 | 3. Linux 命令:`sudo nscd restart` 33 | 34 | **Tips:** 如以上刷新不好使,请重启尝试 35 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/callback/IWorker.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.callback; 2 | 3 | import java.util.Map; 4 | 5 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 6 | 7 | /** 8 | * 每个最小执行单元需要实现该接口 9 | * 10 | * @author wuweifeng wrote on 2019-11-19. 11 | */ 12 | @FunctionalInterface 13 | public interface IWorker { 14 | /** 15 | * 在这里做耗时操作,如rpc请求、IO等 16 | * 17 | * @param object object 18 | * @param allWrappers 任务包装 19 | */ 20 | V action(T object, Map allWrappers); 21 | 22 | /** 23 | * 超时、异常时,返回的默认值 24 | * 25 | * @return 默认值 26 | */ 27 | default V defaultValue() { 28 | return null; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/worker/GetIpAddressWorker.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.worker; 2 | 3 | import com.maxiaofa.hosts.async.callback.IWorker; 4 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 5 | import com.maxiaofa.hosts.constants.IpAddress; 6 | import com.maxiaofa.hosts.utils.HtmlUtils; 7 | 8 | import java.util.Map; 9 | import java.util.Objects; 10 | 11 | /** 12 | * @author MaXiaoFa 13 | */ 14 | public class GetIpAddressWorker implements IWorker { 15 | 16 | @Override 17 | public String action(String url, Map map) { 18 | String htmlResource = HtmlUtils.getUrlHtml(IpAddress.SELECT_IP_ADDRESS_URL+url); 19 | String format = String.format("IP address of %s is ", url); 20 | return String.format("%s %s\n", Objects.requireNonNull(HtmlUtils.parseHtmlGetIpAddress(htmlResource)).replace(format,""),url); 21 | } 22 | 23 | @Override 24 | public String defaultValue() { 25 | return null; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/config/GenContentTheadPoolExecutorConfig.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.config; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | import java.util.concurrent.TimeUnit; 6 | 7 | /** 8 | * @author MaXiaoFa 9 | */ 10 | public class GenContentTheadPoolExecutorConfig { 11 | 12 | private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); 13 | 14 | private static final int corePoolSize = 0; 15 | private static final int maximumPoolSize = CPU_COUNT * 2 + 1; 16 | private static final int keepAliveTime = 10; 17 | 18 | public static ThreadPoolExecutor threadPoolExecutor(){ 19 | ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newCachedThreadPool(); 20 | executor.setMaximumPoolSize(maximumPoolSize); 21 | executor.setCorePoolSize(corePoolSize); 22 | executor.setKeepAliveTime(keepAliveTime, TimeUnit.SECONDS); 23 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 24 | return executor; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/worker/DependWrapper.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.worker; 2 | 3 | 4 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 5 | 6 | /** 7 | * 对依赖的wrapper的封装 8 | * @author wuweifeng wrote on 2019-12-20 9 | * @version 1.0 10 | */ 11 | public class DependWrapper { 12 | private WorkerWrapper dependWrapper; 13 | /** 14 | * 是否该依赖必须完成后才能执行自己.

15 | * 因为存在一个任务,依赖于多个任务,是让这多个任务全部完成后才执行自己,还是某几个执行完毕就可以执行自己 16 | * 如 17 | * 1 18 | * ---3 19 | * 2 20 | * 或 21 | * 1---3 22 | * 2---3 23 | * 这两种就不一样,上面的就是必须12都完毕,才能3 24 | * 下面的就是1完毕就可以3 25 | */ 26 | private boolean must = true; 27 | 28 | public DependWrapper(WorkerWrapper dependWrapper, boolean must) { 29 | this.dependWrapper = dependWrapper; 30 | this.must = must; 31 | } 32 | 33 | public DependWrapper() { 34 | } 35 | 36 | public WorkerWrapper getDependWrapper() { 37 | return dependWrapper; 38 | } 39 | 40 | public void setDependWrapper(WorkerWrapper dependWrapper) { 41 | this.dependWrapper = dependWrapper; 42 | } 43 | 44 | public boolean isMust() { 45 | return must; 46 | } 47 | 48 | public void setMust(boolean must) { 49 | this.must = must; 50 | } 51 | 52 | @Override 53 | public String toString() { 54 | return "DependWrapper{" + 55 | "dependWrapper=" + dependWrapper + 56 | ", must=" + must + 57 | '}'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.utils; 2 | 3 | import com.maxiaofa.hosts.RunHosts; 4 | 5 | import java.io.*; 6 | import java.util.Scanner; 7 | import java.util.logging.Logger; 8 | 9 | /** 10 | * @author MaXiaoFa 11 | */ 12 | public class FileUtils { 13 | 14 | private static final Logger log = Logger.getLogger(RunHosts.class.getName()); 15 | 16 | public static String read(File file){ 17 | StringBuilder content = new StringBuilder(); 18 | try (Scanner sc = new Scanner(new FileReader(file.getName()))) { 19 | while (sc.hasNextLine()) { 20 | content.append(sc.nextLine()) 21 | .append("\n"); 22 | } 23 | } catch (FileNotFoundException e) { 24 | e.printStackTrace(); 25 | } 26 | return content.toString(); 27 | } 28 | 29 | public static void write(File file,String content){ 30 | FileOutputStream fos = null; 31 | try { 32 | if(!file.exists()){ 33 | boolean newFile = file.createNewFile(); 34 | if(newFile)log.info("文件不存在正在创建文件..."); 35 | } 36 | fos = new FileOutputStream(file); 37 | fos.write(content.getBytes()); 38 | } catch (IOException e) { 39 | e.printStackTrace(); 40 | }finally{ 41 | try { 42 | if(null!=fos)fos.close(); 43 | } catch (IOException e) { 44 | e.printStackTrace(); 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/worker/WorkResult.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.worker; 2 | 3 | /** 4 | * 执行结果 5 | */ 6 | public class WorkResult { 7 | /** 8 | * 执行的结果 9 | */ 10 | private V result; 11 | /** 12 | * 结果状态 13 | */ 14 | private ResultState resultState; 15 | private Exception ex; 16 | 17 | public WorkResult(V result, ResultState resultState) { 18 | this(result, resultState, null); 19 | } 20 | 21 | public WorkResult(V result, ResultState resultState, Exception ex) { 22 | this.result = result; 23 | this.resultState = resultState; 24 | this.ex = ex; 25 | } 26 | 27 | public static WorkResult defaultResult() { 28 | return new WorkResult<>(null, ResultState.DEFAULT); 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "WorkResult{" + 34 | "result=" + result + 35 | ", resultState=" + resultState + 36 | ", ex=" + ex + 37 | '}'; 38 | } 39 | 40 | public Exception getEx() { 41 | return ex; 42 | } 43 | 44 | public void setEx(Exception ex) { 45 | this.ex = ex; 46 | } 47 | 48 | public V getResult() { 49 | return result; 50 | } 51 | 52 | public void setResult(V result) { 53 | this.result = result; 54 | } 55 | 56 | public ResultState getResultState() { 57 | return resultState; 58 | } 59 | 60 | public void setResultState(ResultState resultState) { 61 | this.resultState = resultState; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /.github/workflows/updateHosts.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven 3 | 4 | name: Github-Hosts 5 | 6 | on: 7 | push: 8 | schedule: 9 | - cron: '0 0 * * *' 10 | 11 | jobs: 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | 18 | - name: Set up JDK 21 19 | uses: actions/setup-java@v3 20 | with: 21 | java-version: '21' 22 | distribution: 'temurin' 23 | cache: maven 24 | 25 | - name: Build with Maven 26 | run: mvn -B package --file pom.xml 27 | 28 | - name: Run jar Create hosts 29 | run: java -jar target/github-hosts-1.1.jar 30 | 31 | - name: Clear target 32 | run: rm -rf target/ 33 | 34 | - name: Commit 35 | id: commit 36 | run: | 37 | git config --global user.email "github-actions[bot]@users.noreply.github.com" 38 | git config --global user.name "GitHub Actions [Bot]" 39 | git add . 40 | if git diff-index --quiet HEAD --; then 41 | echo "No changes to commit" 42 | else 43 | git commit -m "update hosts and readme" 44 | fi 45 | 46 | - name: Check on failures 47 | if: steps.commit.outputs.status == 'failure' 48 | run: exit 1 49 | 50 | - name: Push changes 51 | uses: ad-m/github-push-action@v0.6.0 52 | with: 53 | github_token: ${{ secrets.GITHUB_TOKEN }} 54 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/utils/HtmlUtils.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.utils; 2 | 3 | import org.jsoup.Jsoup; 4 | import org.jsoup.nodes.Document; 5 | import org.jsoup.nodes.Element; 6 | 7 | import java.io.BufferedReader; 8 | import java.io.IOException; 9 | import java.io.InputStreamReader; 10 | import java.net.URL; 11 | import java.net.URLConnection; 12 | import java.nio.charset.StandardCharsets; 13 | 14 | /** 15 | * @author MaXiaoFa 16 | */ 17 | public class HtmlUtils { 18 | 19 | public static String parseHtmlGetIpAddress(String html){ 20 | Document parseHtml = Jsoup.parse(html); 21 | 22 | Element ipAddress = parseHtml.getElementById("divString0"); 23 | return ipAddress!=null ? ipAddress.html() : null; 24 | } 25 | 26 | public static String getUrlHtml(String url) { 27 | StringBuilder buffer=new StringBuilder(); 28 | InputStreamReader isr=null; 29 | try { 30 | URL urlObj = new URL(url); 31 | 32 | URLConnection uc = urlObj.openConnection(); 33 | 34 | isr =new InputStreamReader(uc.getInputStream(), StandardCharsets.UTF_8); 35 | BufferedReader reader =new BufferedReader(isr); 36 | 37 | String line; 38 | while ((line=reader.readLine())!=null) { 39 | buffer.append(line).append("\n"); 40 | } 41 | } catch (Exception e) { 42 | e.printStackTrace(); 43 | } finally{ 44 | try{ 45 | if(null!=isr)isr.close(); 46 | } catch(IOException e){ 47 | e.printStackTrace(); 48 | } 49 | } 50 | return buffer.toString(); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/executor/timer/SystemClock.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.executor.timer; 2 | 3 | import java.util.concurrent.Executors; 4 | import java.util.concurrent.ScheduledExecutorService; 5 | import java.util.concurrent.ThreadFactory; 6 | import java.util.concurrent.TimeUnit; 7 | import java.util.concurrent.atomic.AtomicLong; 8 | 9 | /** 10 | * 用于解决高并发下System.currentTimeMillis卡顿 11 | * @author lry 12 | */ 13 | public class SystemClock { 14 | 15 | private final int period; 16 | 17 | private final AtomicLong now; 18 | 19 | private static class InstanceHolder { 20 | private static final SystemClock INSTANCE = new SystemClock(1); 21 | } 22 | 23 | private SystemClock(int period) { 24 | this.period = period; 25 | this.now = new AtomicLong(System.currentTimeMillis()); 26 | scheduleClockUpdating(); 27 | } 28 | 29 | private static SystemClock instance() { 30 | return InstanceHolder.INSTANCE; 31 | } 32 | 33 | private void scheduleClockUpdating() { 34 | ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(runnable -> { 35 | Thread thread = new Thread(runnable, "System Clock"); 36 | thread.setDaemon(true); 37 | return thread; 38 | }); 39 | scheduler.scheduleAtFixedRate(() -> now.set(System.currentTimeMillis()), period, period, TimeUnit.MILLISECONDS); 40 | } 41 | 42 | private long currentTimeMillis() { 43 | return now.get(); 44 | } 45 | 46 | /** 47 | * 用来替换原来的System.currentTimeMillis() 48 | */ 49 | public static long now() { 50 | return instance().currentTimeMillis(); 51 | } 52 | } -------------------------------------------------------------------------------- /hosts: -------------------------------------------------------------------------------- 1 | #Github Hosts Start 2 | #Update Time: 2025-04-12 3 | #Project Address: https://github.com/maxiaof/github-hosts 4 | #Update URL: https://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts 5 | 140.82.113.26 alive.github.com 6 | 140.82.113.26 live.github.com 7 | 185.199.111.154 github.githubassets.com 8 | 140.82.112.21 central.github.com 9 | 185.199.110.133 desktop.githubusercontent.com 10 | 185.199.109.133 camo.githubusercontent.com 11 | 185.199.111.133 github.map.fastly.net 12 | 146.75.121.194 github.global.ssl.fastly.net 13 | 140.82.121.4 gist.github.com 14 | 185.199.110.153 github.io 15 | 140.82.121.3 github.com 16 | 192.0.66.2 github.blog 17 | 140.82.121.5 api.github.com 18 | 185.199.109.133 raw.githubusercontent.com 19 | 185.199.110.133 user-images.githubusercontent.com 20 | 185.199.110.133 favicons.githubusercontent.com 21 | 185.199.109.133 avatars5.githubusercontent.com 22 | 185.199.111.133 avatars4.githubusercontent.com 23 | 185.199.109.133 avatars3.githubusercontent.com 24 | 185.199.108.133 avatars2.githubusercontent.com 25 | 185.199.111.133 avatars1.githubusercontent.com 26 | 185.199.111.133 avatars0.githubusercontent.com 27 | 185.199.109.133 avatars.githubusercontent.com 28 | 140.82.121.10 codeload.github.com 29 | 3.5.22.135 github-cloud.s3.amazonaws.com 30 | 52.217.122.33 github-com.s3.amazonaws.com 31 | 54.231.128.25 github-production-release-asset-2e65be.s3.amazonaws.com 32 | 3.5.28.154 github-production-user-asset-6210df.s3.amazonaws.com 33 | 3.5.2.206 github-production-repository-file-5c1aeb.s3.amazonaws.com 34 | 185.199.109.153 githubstatus.com 35 | 140.82.113.17 github.community 36 | 51.137.3.17 github.dev 37 | 140.82.113.21 collector.github.com 38 | 13.107.42.16 pipelines.actions.githubusercontent.com 39 | 185.199.109.133 media.githubusercontent.com 40 | 185.199.111.133 cloud.githubusercontent.com 41 | 185.199.109.133 objects.githubusercontent.com 42 | #Github Hosts End 43 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/constants/GithubUrl.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.constants; 2 | 3 | /** 4 | * @author MaXiaoFa 5 | */ 6 | public class GithubUrl { 7 | 8 | public final static String[] GITHUB_URL = new String[]{ 9 | "alive.github.com", 10 | "live.github.com", 11 | "github.githubassets.com", 12 | "central.github.com", 13 | "desktop.githubusercontent.com", 14 | "assets-cdn.github.com", 15 | "camo.githubusercontent.com", 16 | "github.map.fastly.net", 17 | "github.global.ssl.fastly.net", 18 | "gist.github.com", 19 | "github.io", 20 | "github.com", 21 | "github.blog", 22 | "api.github.com", 23 | "raw.githubusercontent.com", 24 | "user-images.githubusercontent.com", 25 | "favicons.githubusercontent.com", 26 | "avatars5.githubusercontent.com", 27 | "avatars4.githubusercontent.com", 28 | "avatars3.githubusercontent.com", 29 | "avatars2.githubusercontent.com", 30 | "avatars1.githubusercontent.com", 31 | "avatars0.githubusercontent.com", 32 | "avatars.githubusercontent.com", 33 | "codeload.github.com", 34 | "github-cloud.s3.amazonaws.com", 35 | "github-com.s3.amazonaws.com", 36 | "github-production-release-asset-2e65be.s3.amazonaws.com", 37 | "github-production-user-asset-6210df.s3.amazonaws.com", 38 | "github-production-repository-file-5c1aeb.s3.amazonaws.com", 39 | "githubstatus.com", 40 | "github.community", 41 | "github.dev", 42 | "collector.github.com", 43 | "pipelines.actions.githubusercontent.com", 44 | "media.githubusercontent.com", 45 | "cloud.githubusercontent.com", 46 | "objects.githubusercontent.com"}; 47 | } 48 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.maxiaofa.github-hosts 8 | github-hosts 9 | 1.1 10 | 11 | 12 | 21 13 | 21 14 | 15 | 16 | 17 | 18 | 19 | 20 | org.jsoup 21 | jsoup 22 | 1.15.3 23 | 24 | 25 | 26 | 27 | 28 | 29 | maven-assembly-plugin 30 | 31 | false 32 | 33 | jar-with-dependencies 34 | 35 | 36 | 37 | com.maxiaofa.hosts.RunHosts 38 | 39 | 40 | 41 | 42 | 43 | make-assembly 44 | package 45 | 46 | single 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub-Hosts 2 | 3 | ## 一、介绍 4 | 通过修改Hosts解决国内Github经常抽风访问不到 5 | 6 | --- 7 | 8 | ## 二、使用方法 9 | 10 | ### 2.1 复制下面的内容 11 | ```bash 12 | #Github Hosts Start 13 | #Update Time: 2025-04-12 14 | #Project Address: https://github.com/maxiaof/github-hosts 15 | #Update URL: https://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts 16 | 140.82.113.26 alive.github.com 17 | 140.82.113.26 live.github.com 18 | 185.199.111.154 github.githubassets.com 19 | 140.82.112.21 central.github.com 20 | 185.199.110.133 desktop.githubusercontent.com 21 | 185.199.109.133 camo.githubusercontent.com 22 | 185.199.111.133 github.map.fastly.net 23 | 146.75.121.194 github.global.ssl.fastly.net 24 | 140.82.121.4 gist.github.com 25 | 185.199.110.153 github.io 26 | 140.82.121.3 github.com 27 | 192.0.66.2 github.blog 28 | 140.82.121.5 api.github.com 29 | 185.199.109.133 raw.githubusercontent.com 30 | 185.199.110.133 user-images.githubusercontent.com 31 | 185.199.110.133 favicons.githubusercontent.com 32 | 185.199.109.133 avatars5.githubusercontent.com 33 | 185.199.111.133 avatars4.githubusercontent.com 34 | 185.199.109.133 avatars3.githubusercontent.com 35 | 185.199.108.133 avatars2.githubusercontent.com 36 | 185.199.111.133 avatars1.githubusercontent.com 37 | 185.199.111.133 avatars0.githubusercontent.com 38 | 185.199.109.133 avatars.githubusercontent.com 39 | 140.82.121.10 codeload.github.com 40 | 3.5.22.135 github-cloud.s3.amazonaws.com 41 | 52.217.122.33 github-com.s3.amazonaws.com 42 | 54.231.128.25 github-production-release-asset-2e65be.s3.amazonaws.com 43 | 3.5.28.154 github-production-user-asset-6210df.s3.amazonaws.com 44 | 3.5.2.206 github-production-repository-file-5c1aeb.s3.amazonaws.com 45 | 185.199.109.153 githubstatus.com 46 | 140.82.113.17 github.community 47 | 51.137.3.17 github.dev 48 | 140.82.113.21 collector.github.com 49 | 13.107.42.16 pipelines.actions.githubusercontent.com 50 | 185.199.109.133 media.githubusercontent.com 51 | 185.199.111.133 cloud.githubusercontent.com 52 | 185.199.109.133 objects.githubusercontent.com 53 | #Github Hosts End 54 | 55 | ``` 56 | 最后更新时间:`2025-04-12` 57 | 58 | ## 2.2 修改 hosts 文件 59 | hosts 文件在不同系统位置不一,详情如下: 60 | - Windows 系统:`C:\Windows\System32\drivers\etc\hosts`。 61 | - Mac(苹果电脑)系统:`/etc/hosts`。 62 | - Linux 系统:`/etc/hosts`。 63 | 64 | 修改方法,把2.1的内容复制到文本末尾: 65 | 66 | 1. Windows 使用记事本。 67 | 2. Linux、Mac 使用 Root 权限:`sudo vi /etc/hosts`。 68 | 69 | #### 2.3 激活生效 70 | 大部分情况下是直接生效,如未生效可尝试下面的办法,刷新 DNS: 71 | 72 | 1. Windows:在 CMD 窗口输入:`ipconfig /flushdns` 73 | 2. Mac 命令:`sudo killall -HUP mDNSResponder` 74 | 3. Linux 命令:`sudo nscd restart` 75 | 76 | **Tips:** 如以上刷新不好使,请重启尝试 77 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/RunHosts.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts; 2 | 3 | import com.maxiaofa.hosts.async.executor.Async; 4 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 5 | import com.maxiaofa.hosts.config.GenContentTheadPoolExecutorConfig; 6 | import com.maxiaofa.hosts.constants.GithubUrl; 7 | import com.maxiaofa.hosts.utils.FileUtils; 8 | import com.maxiaofa.hosts.worker.GetIpAddressWorker; 9 | 10 | import java.io.File; 11 | import java.time.LocalDate; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | import java.util.concurrent.ExecutionException; 15 | import java.util.logging.Logger; 16 | import java.util.regex.Matcher; 17 | import java.util.regex.Pattern; 18 | 19 | 20 | /** 21 | * @author MaXiaoFa 22 | */ 23 | public class RunHosts { 24 | 25 | private static final Logger log = Logger.getLogger(RunHosts.class.getName()); 26 | 27 | public static void main(String[] args) throws ExecutionException, InterruptedException { 28 | log.info("正在获取最新IP Address..."); 29 | 30 | File file = new File(System.getProperty("user.dir") + "/hosts"); 31 | 32 | LocalDate updateTime = LocalDate.now(); 33 | StringBuilder content = new StringBuilder(); 34 | 35 | content.append("#Github Hosts Start\n"); 36 | content.append(String.format("#Update Time: %s\n", updateTime)); 37 | content.append("#Project Address: https://github.com/maxiaof/github-hosts\n"); 38 | content.append("#Update URL: https://raw.githubusercontent.com/maxiaof/github-hosts/master/hosts\n"); 39 | 40 | List> workerWrapperList = new ArrayList<>(); 41 | 42 | for (int i = 0; i < GithubUrl.GITHUB_URL.length; i++) { 43 | GetIpAddressWorker getIpAddressWorker = new GetIpAddressWorker(); 44 | WorkerWrapper build = new WorkerWrapper.Builder() 45 | .worker(getIpAddressWorker) 46 | .param(GithubUrl.GITHUB_URL[i]) 47 | .build(); 48 | workerWrapperList.add(build); 49 | } 50 | 51 | Async.beginWork(10000, GenContentTheadPoolExecutorConfig.threadPoolExecutor(), workerWrapperList.toArray(new WorkerWrapper[]{})); 52 | 53 | workerWrapperList.forEach(workerWrapper -> { 54 | String result = workerWrapper.getWorkResult().getResult(); 55 | if(result != null){ 56 | String pattern = "((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})(\\.((2(5[0-5]|[0-4]\\d))|[0-1]?\\d{1,2})){3}"; 57 | 58 | Pattern r = Pattern.compile(pattern); 59 | Matcher m = r.matcher(result); 60 | 61 | if(m.find()){ 62 | content.append(result); 63 | } 64 | } 65 | }); 66 | 67 | Async.shutDown(); 68 | 69 | content.append("#Github Hosts End\n"); 70 | FileUtils.write(file,content.toString()); 71 | 72 | log.info("正在更新README文件..."); 73 | updateReadme(updateTime, content.toString()); 74 | 75 | log.info("操作完成,正在关闭..."); 76 | } 77 | 78 | private static void updateReadme(LocalDate data,String hosts){ 79 | File readmeTemplate = new File(System.getProperty("user.dir") + "/README_TEMPLATE.md"); 80 | String readmeTemplateContent = FileUtils.read(readmeTemplate); 81 | 82 | String updateReadmeContent = readmeTemplateContent.replace("{[update_time]}", data.toString()).replace("{[hosts]}", hosts); 83 | 84 | File readme = new File(System.getProperty("user.dir") + "/README.md"); 85 | FileUtils.write(readme,updateReadmeContent); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/executor/Async.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.executor; 2 | 3 | 4 | import com.maxiaofa.hosts.async.callback.DefaultGroupCallback; 5 | import com.maxiaofa.hosts.async.callback.IGroupCallback; 6 | import com.maxiaofa.hosts.async.wrapper.WorkerWrapper; 7 | 8 | import java.util.*; 9 | import java.util.concurrent.*; 10 | import java.util.stream.Collectors; 11 | 12 | /** 13 | * 类入口,可以根据自己情况调整core线程的数量 14 | * @author wuweifeng wrote on 2019-12-18 15 | * @version 1.0 16 | */ 17 | public class Async { 18 | /** 19 | * 默认不定长线程池 20 | */ 21 | private static final ThreadPoolExecutor COMMON_POOL = (ThreadPoolExecutor) Executors.newCachedThreadPool(); 22 | /** 23 | * 注意,这里是个static,也就是只能有一个线程池。用户自定义线程池时,也只能定义一个 24 | */ 25 | private static ExecutorService executorService; 26 | 27 | /** 28 | * 出发点 29 | */ 30 | public static boolean beginWork(long timeout, ExecutorService executorService, List workerWrappers) throws ExecutionException, InterruptedException { 31 | if(workerWrappers == null || workerWrappers.size() == 0) { 32 | return false; 33 | } 34 | //保存线程池变量 35 | Async.executorService = executorService; 36 | //定义一个map,存放所有的wrapper,key为wrapper的唯一id,value是该wrapper,可以从value中获取wrapper的result 37 | Map forParamUseWrappers = new ConcurrentHashMap<>(); 38 | CompletableFuture[] futures = new CompletableFuture[workerWrappers.size()]; 39 | for (int i = 0; i < workerWrappers.size(); i++) { 40 | WorkerWrapper wrapper = workerWrappers.get(i); 41 | futures[i] = CompletableFuture.runAsync(() -> wrapper.work(executorService, timeout, forParamUseWrappers), executorService); 42 | } 43 | try { 44 | CompletableFuture.allOf(futures).get(timeout, TimeUnit.MILLISECONDS); 45 | return true; 46 | } catch (TimeoutException e) { 47 | Set set = new HashSet<>(); 48 | totalWorkers(workerWrappers, set); 49 | for (WorkerWrapper wrapper : set) { 50 | wrapper.stopNow(); 51 | } 52 | return false; 53 | } 54 | } 55 | 56 | /** 57 | * 如果想自定义线程池,请传pool。不自定义的话,就走默认的COMMON_POOL 58 | */ 59 | public static boolean beginWork(long timeout, ExecutorService executorService, WorkerWrapper... workerWrapper) throws ExecutionException, InterruptedException { 60 | if(workerWrapper == null || workerWrapper.length == 0) { 61 | return false; 62 | } 63 | List workerWrappers = Arrays.stream(workerWrapper).collect(Collectors.toList()); 64 | return beginWork(timeout, executorService, workerWrappers); 65 | } 66 | 67 | /** 68 | * 同步阻塞,直到所有都完成,或失败 69 | */ 70 | public static boolean beginWork(long timeout, WorkerWrapper... workerWrapper) throws ExecutionException, InterruptedException { 71 | return beginWork(timeout, COMMON_POOL, workerWrapper); 72 | } 73 | 74 | public static void beginWorkAsync(long timeout, IGroupCallback groupCallback, WorkerWrapper... workerWrapper) { 75 | beginWorkAsync(timeout, COMMON_POOL, groupCallback, workerWrapper); 76 | } 77 | 78 | /** 79 | * 异步执行,直到所有都完成,或失败后,发起回调 80 | */ 81 | public static void beginWorkAsync(long timeout, ExecutorService executorService, IGroupCallback groupCallback, WorkerWrapper... workerWrapper) { 82 | if (groupCallback == null) { 83 | groupCallback = new DefaultGroupCallback(); 84 | } 85 | IGroupCallback finalGroupCallback = groupCallback; 86 | if (executorService != null) { 87 | executorService.submit(() -> { 88 | try { 89 | boolean success = beginWork(timeout, executorService, workerWrapper); 90 | if (success) { 91 | finalGroupCallback.success(Arrays.asList(workerWrapper)); 92 | } else { 93 | finalGroupCallback.failure(Arrays.asList(workerWrapper), new TimeoutException()); 94 | } 95 | } catch (ExecutionException | InterruptedException e) { 96 | e.printStackTrace(); 97 | finalGroupCallback.failure(Arrays.asList(workerWrapper), e); 98 | } 99 | }); 100 | } else { 101 | COMMON_POOL.submit(() -> { 102 | try { 103 | boolean success = beginWork(timeout, COMMON_POOL, workerWrapper); 104 | if (success) { 105 | finalGroupCallback.success(Arrays.asList(workerWrapper)); 106 | } else { 107 | finalGroupCallback.failure(Arrays.asList(workerWrapper), new TimeoutException()); 108 | } 109 | } catch (ExecutionException | InterruptedException e) { 110 | e.printStackTrace(); 111 | finalGroupCallback.failure(Arrays.asList(workerWrapper), e); 112 | } 113 | }); 114 | } 115 | 116 | } 117 | 118 | /** 119 | * 总共多少个执行单元 120 | */ 121 | @SuppressWarnings("unchecked") 122 | private static void totalWorkers(List workerWrappers, Set set) { 123 | set.addAll(workerWrappers); 124 | for (WorkerWrapper wrapper : workerWrappers) { 125 | if (wrapper.getNextWrappers() == null) { 126 | continue; 127 | } 128 | List wrappers = wrapper.getNextWrappers(); 129 | totalWorkers(wrappers, set); 130 | } 131 | 132 | } 133 | 134 | /** 135 | * 关闭线程池 136 | */ 137 | public static void shutDown() { 138 | shutDown(executorService); 139 | } 140 | 141 | /** 142 | * 关闭线程池 143 | */ 144 | public static void shutDown(ExecutorService executorService) { 145 | if (executorService != null) { 146 | executorService.shutdown(); 147 | } else { 148 | COMMON_POOL.shutdown(); 149 | } 150 | } 151 | 152 | public static String getThreadCount() { 153 | return "activeCount=" + COMMON_POOL.getActiveCount() + 154 | " completedCount " + COMMON_POOL.getCompletedTaskCount() + 155 | " largestCount " + COMMON_POOL.getLargestPoolSize(); 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 木兰宽松许可证, 第2版 2 | 3 | 木兰宽松许可证, 第2版 4 | 2020年1月 http://license.coscl.org.cn/MulanPSL2 5 | 6 | 7 | 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束: 8 | 9 | 0. 定义 10 | 11 | “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。 12 | 13 | “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。 14 | 15 | “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。 16 | 17 | “法人实体”是指提交贡献的机构及其“关联实体”。 18 | 19 | “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。 20 | 21 | 1. 授予版权许可 22 | 23 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。 24 | 25 | 2. 授予专利许可 26 | 27 | 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。 28 | 29 | 3. 无商标许可 30 | 31 | “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。 32 | 33 | 4. 分发限制 34 | 35 | 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。 36 | 37 | 5. 免责声明与责任限制 38 | 39 | “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。 40 | 41 | 6. 语言 42 | “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。 43 | 44 | 条款结束 45 | 46 | 如何将木兰宽松许可证,第2版,应用到您的软件 47 | 48 | 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步: 49 | 50 | 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字; 51 | 52 | 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中; 53 | 54 | 3, 请将如下声明文本放入每个源文件的头部注释中。 55 | 56 | Copyright (c) [Year] [name of copyright holder] 57 | [Software Name] is licensed under Mulan PSL v2. 58 | You can use this software according to the terms and conditions of the Mulan PSL v2. 59 | You may obtain a copy of Mulan PSL v2 at: 60 | http://license.coscl.org.cn/MulanPSL2 61 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 62 | See the Mulan PSL v2 for more details. 63 | 64 | 65 | Mulan Permissive Software License,Version 2 66 | 67 | Mulan Permissive Software License,Version 2 (Mulan PSL v2) 68 | January 2020 http://license.coscl.org.cn/MulanPSL2 69 | 70 | Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions: 71 | 72 | 0. Definition 73 | 74 | Software means the program and related documents which are licensed under this License and comprise all Contribution(s). 75 | 76 | Contribution means the copyrightable work licensed by a particular Contributor under this License. 77 | 78 | Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License. 79 | 80 | Legal Entity means the entity making a Contribution and all its Affiliates. 81 | 82 | Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity. 83 | 84 | 1. Grant of Copyright License 85 | 86 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not. 87 | 88 | 2. Grant of Patent License 89 | 90 | Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken. 91 | 92 | 3. No Trademark License 93 | 94 | No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4. 95 | 96 | 4. Distribution Restriction 97 | 98 | You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software. 99 | 100 | 5. Disclaimer of Warranty and Limitation of Liability 101 | 102 | THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 103 | 104 | 6. Language 105 | 106 | THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL. 107 | 108 | END OF THE TERMS AND CONDITIONS 109 | 110 | How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software 111 | 112 | To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps: 113 | 114 | i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner; 115 | 116 | ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package; 117 | 118 | iii Attach the statement to the appropriate annotated syntax at the beginning of each source file. 119 | 120 | 121 | Copyright (c) [Year] [name of copyright holder] 122 | [Software Name] is licensed under Mulan PSL v2. 123 | You can use this software according to the terms and conditions of the Mulan PSL v2. 124 | You may obtain a copy of Mulan PSL v2 at: 125 | http://license.coscl.org.cn/MulanPSL2 126 | THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE. 127 | See the Mulan PSL v2 for more details. 128 | -------------------------------------------------------------------------------- /src/main/java/com/maxiaofa/hosts/async/wrapper/WorkerWrapper.java: -------------------------------------------------------------------------------- 1 | package com.maxiaofa.hosts.async.wrapper; 2 | 3 | import com.maxiaofa.hosts.async.callback.DefaultCallback; 4 | import com.maxiaofa.hosts.async.callback.ICallback; 5 | import com.maxiaofa.hosts.async.callback.IWorker; 6 | import com.maxiaofa.hosts.async.exception.SkippedException; 7 | import com.maxiaofa.hosts.async.executor.timer.SystemClock; 8 | import com.maxiaofa.hosts.async.worker.DependWrapper; 9 | import com.maxiaofa.hosts.async.worker.ResultState; 10 | import com.maxiaofa.hosts.async.worker.WorkResult; 11 | 12 | import java.util.*; 13 | import java.util.concurrent.CompletableFuture; 14 | import java.util.concurrent.ExecutorService; 15 | import java.util.concurrent.TimeUnit; 16 | import java.util.concurrent.atomic.AtomicInteger; 17 | 18 | /** 19 | * 对每个worker及callback进行包装,一对一 20 | * 21 | * @author wuweifeng wrote on 2019-11-19. 22 | */ 23 | public class WorkerWrapper { 24 | /** 25 | * 该wrapper的唯一标识 26 | */ 27 | private String id; 28 | /** 29 | * worker将来要处理的param 30 | */ 31 | private T param; 32 | private IWorker worker; 33 | private ICallback callback; 34 | /** 35 | * 在自己后面的wrapper,如果没有,自己就是末尾;如果有一个,就是串行;如果有多个,有几个就需要开几个线程

36 | * -------2 37 | * 1 38 | * -------3 39 | * 如1后面有2、3 40 | */ 41 | private List> nextWrappers; 42 | /** 43 | * 依赖的wrappers,有2种情况,1:必须依赖的全部完成后,才能执行自己 2:依赖的任何一个、多个完成了,就可以执行自己 44 | * 通过must字段来控制是否依赖项必须完成 45 | * 1 46 | * -------3 47 | * 2 48 | * 1、2执行完毕后才能执行3 49 | */ 50 | private List dependWrappers; 51 | /** 52 | * 标记该事件是否已经被处理过了,譬如已经超时返回false了,后续rpc又收到返回值了,则不再二次回调 53 | * 经试验,volatile并不能保证"同一毫秒"内,多线程对该值的修改和拉取 54 | *

55 | * 1-finish, 2-error, 3-working 56 | */ 57 | private AtomicInteger state = new AtomicInteger(0); 58 | /** 59 | * 该map存放所有wrapper的id和wrapper映射 60 | */ 61 | private Map forParamUseWrappers; 62 | /** 63 | * 也是个钩子变量,用来存临时的结果 64 | */ 65 | private volatile WorkResult workResult = WorkResult.defaultResult(); 66 | /** 67 | * 是否在执行自己前,去校验nextWrapper的执行结果

68 | * 1 4 69 | * -------3 70 | * 2 71 | * 如这种在4执行前,可能3已经执行完毕了(被2执行完后触发的),那么4就没必要执行了。 72 | * 注意,该属性仅在nextWrapper数量<=1时有效,>1时的情况是不存在的 73 | */ 74 | private volatile boolean needCheckNextWrapperResult = true; 75 | 76 | private static final int FINISH = 1; 77 | private static final int ERROR = 2; 78 | private static final int WORKING = 3; 79 | private static final int INIT = 0; 80 | 81 | private WorkerWrapper(String id, IWorker worker, T param, ICallback callback) { 82 | if (worker == null) { 83 | throw new NullPointerException("async.worker is null"); 84 | } 85 | this.worker = worker; 86 | this.param = param; 87 | this.id = id; 88 | //允许不设置回调 89 | if (callback == null) { 90 | callback = new DefaultCallback<>(); 91 | } 92 | this.callback = callback; 93 | } 94 | 95 | /** 96 | * 开始工作 97 | * fromWrapper代表这次work是由哪个上游wrapper发起的 98 | */ 99 | private void work(ExecutorService executorService, WorkerWrapper fromWrapper, long remainTime, Map forParamUseWrappers) { 100 | this.forParamUseWrappers = forParamUseWrappers; 101 | //将自己放到所有wrapper的集合里去 102 | forParamUseWrappers.put(id, this); 103 | long now = SystemClock.now(); 104 | //总的已经超时了,就快速失败,进行下一个 105 | if (remainTime <= 0) { 106 | fastFail(INIT, null); 107 | beginNext(executorService, now, remainTime); 108 | return; 109 | } 110 | //如果自己已经执行过了。 111 | //可能有多个依赖,其中的一个依赖已经执行完了,并且自己也已开始执行或执行完毕。当另一个依赖执行完毕,又进来该方法时,就不重复处理了 112 | if (getState() == FINISH || getState() == ERROR) { 113 | beginNext(executorService, now, remainTime); 114 | return; 115 | } 116 | 117 | //如果在执行前需要校验nextWrapper的状态 118 | if (needCheckNextWrapperResult) { 119 | //如果自己的next链上有已经出结果或已经开始执行的任务了,自己就不用继续了 120 | if (!checkNextWrapperResult()) { 121 | fastFail(INIT, new SkippedException()); 122 | beginNext(executorService, now, remainTime); 123 | return; 124 | } 125 | } 126 | 127 | //如果没有任何依赖,说明自己就是第一批要执行的 128 | if (dependWrappers == null || dependWrappers.size() == 0) { 129 | fire(); 130 | beginNext(executorService, now, remainTime); 131 | return; 132 | } 133 | 134 | /*如果有前方依赖,存在两种情况 135 | 一种是前面只有一个wrapper。即 A -> B 136 | 一种是前面有多个wrapper。A C D -> B。需要A、C、D都完成了才能轮到B。但是无论是A执行完,还是C执行完,都会去唤醒B。 137 | 所以需要B来做判断,必须A、C、D都完成,自己才能执行 */ 138 | 139 | //只有一个依赖 140 | if (dependWrappers.size() == 1) { 141 | doDependsOneJob(fromWrapper); 142 | beginNext(executorService, now, remainTime); 143 | } else { 144 | //有多个依赖时 145 | doDependsJobs(executorService, dependWrappers, fromWrapper, now, remainTime); 146 | } 147 | 148 | } 149 | 150 | 151 | public void work(ExecutorService executorService, long remainTime, Map forParamUseWrappers) { 152 | work(executorService, null, remainTime, forParamUseWrappers); 153 | } 154 | 155 | /** 156 | * 总控制台超时,停止所有任务 157 | */ 158 | public void stopNow() { 159 | if (getState() == INIT || getState() == WORKING) { 160 | fastFail(getState(), null); 161 | } 162 | } 163 | 164 | /** 165 | * 判断自己下游链路上,是否存在已经出结果的或已经开始执行的 166 | * 如果没有返回true,如果有返回false 167 | */ 168 | private boolean checkNextWrapperResult() { 169 | //如果自己就是最后一个,或者后面有并行的多个,就返回true 170 | if (nextWrappers == null || nextWrappers.size() != 1) { 171 | return getState() == INIT; 172 | } 173 | WorkerWrapper nextWrapper = nextWrappers.get(0); 174 | boolean state = nextWrapper.getState() == INIT; 175 | //继续校验自己的next的状态 176 | return state && nextWrapper.checkNextWrapperResult(); 177 | } 178 | 179 | /** 180 | * 进行下一个任务 181 | */ 182 | private void beginNext(ExecutorService executorService, long now, long remainTime) { 183 | //花费的时间 184 | long costTime = SystemClock.now() - now; 185 | if (nextWrappers == null) { 186 | return; 187 | } 188 | if (nextWrappers.size() == 1) { 189 | nextWrappers.get(0).work(executorService, WorkerWrapper.this, remainTime - costTime, forParamUseWrappers); 190 | return; 191 | } 192 | CompletableFuture[] futures = new CompletableFuture[nextWrappers.size()]; 193 | for (int i = 0; i < nextWrappers.size(); i++) { 194 | int finalI = i; 195 | futures[i] = CompletableFuture.runAsync(() -> nextWrappers.get(finalI) 196 | .work(executorService, WorkerWrapper.this, remainTime - costTime, forParamUseWrappers), executorService); 197 | } 198 | try { 199 | CompletableFuture.allOf(futures).get(remainTime - costTime, TimeUnit.MILLISECONDS); 200 | } catch (Exception e) { 201 | e.printStackTrace(); 202 | } 203 | } 204 | 205 | private void doDependsOneJob(WorkerWrapper dependWrapper) { 206 | if (ResultState.TIMEOUT == dependWrapper.getWorkResult().getResultState()) { 207 | workResult = defaultResult(); 208 | fastFail(INIT, null); 209 | } else if (ResultState.EXCEPTION == dependWrapper.getWorkResult().getResultState()) { 210 | workResult = defaultExResult(dependWrapper.getWorkResult().getEx()); 211 | fastFail(INIT, null); 212 | } else { 213 | //前面任务正常完毕了,该自己了 214 | fire(); 215 | } 216 | } 217 | 218 | private synchronized void doDependsJobs(ExecutorService executorService, List dependWrappers, WorkerWrapper fromWrapper, long now, long remainTime) { 219 | //如果当前任务已经完成了,依赖的其他任务拿到锁再进来时,不需要执行下面的逻辑了。 220 | if (getState() != INIT) { 221 | return; 222 | } 223 | boolean nowDependIsMust = false; 224 | //创建必须完成的上游wrapper集合 225 | Set mustWrapper = new HashSet<>(); 226 | for (DependWrapper dependWrapper : dependWrappers) { 227 | if (dependWrapper.isMust()) { 228 | mustWrapper.add(dependWrapper); 229 | } 230 | if (dependWrapper.getDependWrapper().equals(fromWrapper)) { 231 | nowDependIsMust = dependWrapper.isMust(); 232 | } 233 | } 234 | 235 | //如果全部是不必须的条件,那么只要到了这里,就执行自己。 236 | if (mustWrapper.size() == 0) { 237 | if (ResultState.TIMEOUT == fromWrapper.getWorkResult().getResultState()) { 238 | fastFail(INIT, null); 239 | } else { 240 | fire(); 241 | } 242 | beginNext(executorService, now, remainTime); 243 | return; 244 | } 245 | 246 | //如果存在需要必须完成的,且fromWrapper不是必须的,就什么也不干 247 | if (!nowDependIsMust) { 248 | return; 249 | } 250 | 251 | //如果fromWrapper是必须的 252 | boolean existNoFinish = false; 253 | boolean hasError = false; 254 | //先判断前面必须要执行的依赖任务的执行结果,如果有任何一个失败,那就不用走action了,直接给自己设置为失败,进行下一步就是了 255 | for (DependWrapper dependWrapper : mustWrapper) { 256 | WorkerWrapper workerWrapper = dependWrapper.getDependWrapper(); 257 | WorkResult tempWorkResult = workerWrapper.getWorkResult(); 258 | //为null或者isWorking,说明它依赖的某个任务还没执行到或没执行完 259 | if (workerWrapper.getState() == INIT || workerWrapper.getState() == WORKING) { 260 | existNoFinish = true; 261 | break; 262 | } 263 | if (ResultState.TIMEOUT == tempWorkResult.getResultState()) { 264 | workResult = defaultResult(); 265 | hasError = true; 266 | break; 267 | } 268 | if (ResultState.EXCEPTION == tempWorkResult.getResultState()) { 269 | workResult = defaultExResult(workerWrapper.getWorkResult().getEx()); 270 | hasError = true; 271 | break; 272 | } 273 | 274 | } 275 | //只要有失败的 276 | if (hasError) { 277 | fastFail(INIT, null); 278 | beginNext(executorService, now, remainTime); 279 | return; 280 | } 281 | 282 | //如果上游都没有失败,分为两种情况,一种是都finish了,一种是有的在working 283 | //都finish的话 284 | if (!existNoFinish) { 285 | //上游都finish了,进行自己 286 | fire(); 287 | beginNext(executorService, now, remainTime); 288 | return; 289 | } 290 | } 291 | 292 | /** 293 | * 执行自己的job.具体的执行是在另一个线程里,但判断阻塞超时是在work线程 294 | */ 295 | private void fire() { 296 | //阻塞取结果 297 | workResult = workerDoJob(); 298 | } 299 | 300 | /** 301 | * 快速失败 302 | */ 303 | private boolean fastFail(int expect, Exception e) { 304 | //试图将它从expect状态,改成Error 305 | if (!compareAndSetState(expect, ERROR)) { 306 | return false; 307 | } 308 | 309 | //尚未处理过结果 310 | if (checkIsNullResult()) { 311 | if (e == null) { 312 | workResult = defaultResult(); 313 | } else { 314 | workResult = defaultExResult(e); 315 | } 316 | } 317 | 318 | callback.result(false, param, workResult); 319 | return true; 320 | } 321 | 322 | /** 323 | * 具体的单个worker执行任务 324 | */ 325 | private WorkResult workerDoJob() { 326 | //避免重复执行 327 | if (!checkIsNullResult()) { 328 | return workResult; 329 | } 330 | try { 331 | //如果已经不是init状态了,说明正在被执行或已执行完毕。这一步很重要,可以保证任务不被重复执行 332 | if (!compareAndSetState(INIT, WORKING)) { 333 | return workResult; 334 | } 335 | 336 | callback.begin(); 337 | 338 | //执行耗时操作 339 | V resultValue = worker.action(param, forParamUseWrappers); 340 | 341 | //如果状态不是在working,说明别的地方已经修改了 342 | if (!compareAndSetState(WORKING, FINISH)) { 343 | return workResult; 344 | } 345 | 346 | workResult.setResultState(ResultState.SUCCESS); 347 | workResult.setResult(resultValue); 348 | //回调成功 349 | callback.result(true, param, workResult); 350 | 351 | return workResult; 352 | } catch (Exception e) { 353 | //避免重复回调 354 | if (!checkIsNullResult()) { 355 | return workResult; 356 | } 357 | fastFail(WORKING, e); 358 | return workResult; 359 | } 360 | } 361 | 362 | public WorkResult getWorkResult() { 363 | return workResult; 364 | } 365 | 366 | public List> getNextWrappers() { 367 | return nextWrappers; 368 | } 369 | 370 | public void setParam(T param) { 371 | this.param = param; 372 | } 373 | 374 | private boolean checkIsNullResult() { 375 | return ResultState.DEFAULT == workResult.getResultState(); 376 | } 377 | 378 | private void addDepend(WorkerWrapper workerWrapper, boolean must) { 379 | addDepend(new DependWrapper(workerWrapper, must)); 380 | } 381 | 382 | private void addDepend(DependWrapper dependWrapper) { 383 | if (dependWrappers == null) { 384 | dependWrappers = new ArrayList<>(); 385 | } 386 | //如果依赖的是重复的同一个,就不重复添加了 387 | for (DependWrapper wrapper : dependWrappers) { 388 | if (wrapper.equals(dependWrapper)) { 389 | return; 390 | } 391 | } 392 | dependWrappers.add(dependWrapper); 393 | } 394 | 395 | private void addNext(WorkerWrapper workerWrapper) { 396 | if (nextWrappers == null) { 397 | nextWrappers = new ArrayList<>(); 398 | } 399 | //避免添加重复 400 | for (WorkerWrapper wrapper : nextWrappers) { 401 | if (workerWrapper.equals(wrapper)) { 402 | return; 403 | } 404 | } 405 | nextWrappers.add(workerWrapper); 406 | } 407 | 408 | private void addNextWrappers(List> wrappers) { 409 | if (wrappers == null) { 410 | return; 411 | } 412 | for (WorkerWrapper wrapper : wrappers) { 413 | addNext(wrapper); 414 | } 415 | } 416 | 417 | private void addDependWrappers(List dependWrappers) { 418 | if (dependWrappers == null) { 419 | return; 420 | } 421 | for (DependWrapper wrapper : dependWrappers) { 422 | addDepend(wrapper); 423 | } 424 | } 425 | 426 | private WorkResult defaultResult() { 427 | workResult.setResultState(ResultState.TIMEOUT); 428 | workResult.setResult(worker.defaultValue()); 429 | return workResult; 430 | } 431 | 432 | private WorkResult defaultExResult(Exception ex) { 433 | workResult.setResultState(ResultState.EXCEPTION); 434 | workResult.setResult(worker.defaultValue()); 435 | workResult.setEx(ex); 436 | return workResult; 437 | } 438 | 439 | 440 | private int getState() { 441 | return state.get(); 442 | } 443 | 444 | public String getId() { 445 | return id; 446 | } 447 | 448 | private boolean compareAndSetState(int expect, int update) { 449 | return this.state.compareAndSet(expect, update); 450 | } 451 | 452 | private void setNeedCheckNextWrapperResult(boolean needCheckNextWrapperResult) { 453 | this.needCheckNextWrapperResult = needCheckNextWrapperResult; 454 | } 455 | 456 | @Override 457 | public boolean equals(Object o) { 458 | if (this == o) { 459 | return true; 460 | } 461 | if (o == null || getClass() != o.getClass()) { 462 | return false; 463 | } 464 | WorkerWrapper that = (WorkerWrapper) o; 465 | return needCheckNextWrapperResult == that.needCheckNextWrapperResult && 466 | Objects.equals(param, that.param) && 467 | Objects.equals(worker, that.worker) && 468 | Objects.equals(callback, that.callback) && 469 | Objects.equals(nextWrappers, that.nextWrappers) && 470 | Objects.equals(dependWrappers, that.dependWrappers) && 471 | Objects.equals(state, that.state) && 472 | Objects.equals(workResult, that.workResult); 473 | } 474 | 475 | @Override 476 | public int hashCode() { 477 | return Objects.hash(param, worker, callback, nextWrappers, dependWrappers, state, workResult, needCheckNextWrapperResult); 478 | } 479 | 480 | public static class Builder { 481 | /** 482 | * 该wrapper的唯一标识 483 | */ 484 | private String id = UUID.randomUUID().toString(); 485 | /** 486 | * worker将来要处理的param 487 | */ 488 | private W param; 489 | private IWorker worker; 490 | private ICallback callback; 491 | /** 492 | * 自己后面的所有 493 | */ 494 | private List> nextWrappers; 495 | /** 496 | * 自己依赖的所有 497 | */ 498 | private List dependWrappers; 499 | /** 500 | * 存储强依赖于自己的wrapper集合 501 | */ 502 | private Set> selfIsMustSet; 503 | 504 | private boolean needCheckNextWrapperResult = true; 505 | 506 | public Builder worker(IWorker worker) { 507 | this.worker = worker; 508 | return this; 509 | } 510 | 511 | public Builder param(W w) { 512 | this.param = w; 513 | return this; 514 | } 515 | 516 | public Builder id(String id) { 517 | if (id != null) { 518 | this.id = id; 519 | } 520 | return this; 521 | } 522 | 523 | public Builder needCheckNextWrapperResult(boolean needCheckNextWrapperResult) { 524 | this.needCheckNextWrapperResult = needCheckNextWrapperResult; 525 | return this; 526 | } 527 | 528 | public Builder callback(ICallback callback) { 529 | this.callback = callback; 530 | return this; 531 | } 532 | 533 | public Builder depend(WorkerWrapper... wrappers) { 534 | if (wrappers == null) { 535 | return this; 536 | } 537 | for (WorkerWrapper wrapper : wrappers) { 538 | depend(wrapper); 539 | } 540 | return this; 541 | } 542 | 543 | public Builder depend(WorkerWrapper wrapper) { 544 | return depend(wrapper, true); 545 | } 546 | 547 | public Builder depend(WorkerWrapper wrapper, boolean isMust) { 548 | if (wrapper == null) { 549 | return this; 550 | } 551 | DependWrapper dependWrapper = new DependWrapper(wrapper, isMust); 552 | if (dependWrappers == null) { 553 | dependWrappers = new ArrayList<>(); 554 | } 555 | dependWrappers.add(dependWrapper); 556 | return this; 557 | } 558 | 559 | public Builder next(WorkerWrapper wrapper) { 560 | return next(wrapper, true); 561 | } 562 | 563 | public Builder next(WorkerWrapper wrapper, boolean selfIsMust) { 564 | if (nextWrappers == null) { 565 | nextWrappers = new ArrayList<>(); 566 | } 567 | nextWrappers.add(wrapper); 568 | 569 | //强依赖自己 570 | if (selfIsMust) { 571 | if (selfIsMustSet == null) { 572 | selfIsMustSet = new HashSet<>(); 573 | } 574 | selfIsMustSet.add(wrapper); 575 | } 576 | return this; 577 | } 578 | 579 | public Builder next(WorkerWrapper... wrappers) { 580 | if (wrappers == null) { 581 | return this; 582 | } 583 | for (WorkerWrapper wrapper : wrappers) { 584 | next(wrapper); 585 | } 586 | return this; 587 | } 588 | 589 | public WorkerWrapper build() { 590 | WorkerWrapper wrapper = new WorkerWrapper<>(id, worker, param, callback); 591 | wrapper.setNeedCheckNextWrapperResult(needCheckNextWrapperResult); 592 | if (dependWrappers != null) { 593 | for (DependWrapper workerWrapper : dependWrappers) { 594 | workerWrapper.getDependWrapper().addNext(wrapper); 595 | wrapper.addDepend(workerWrapper); 596 | } 597 | } 598 | if (nextWrappers != null) { 599 | for (WorkerWrapper workerWrapper : nextWrappers) { 600 | boolean must = false; 601 | if (selfIsMustSet != null && selfIsMustSet.contains(workerWrapper)) { 602 | must = true; 603 | } 604 | workerWrapper.addDepend(wrapper, must); 605 | wrapper.addNext(workerWrapper); 606 | } 607 | } 608 | 609 | return wrapper; 610 | } 611 | 612 | } 613 | } 614 | --------------------------------------------------------------------------------