├── .gitignore ├── LICENSE ├── README.md ├── pom.xml └── src └── main ├── java └── com │ └── chrisnewland │ └── jepmap │ ├── JEP.java │ ├── JEPMap.java │ ├── JEPProcessor.java │ ├── Project.java │ └── websocket │ ├── FullJEPServer.java │ ├── JEPLoader.java │ └── WebsocketServerEndpoint.java └── resources ├── badmappings.properties ├── menu.html ├── style_jepmap.css └── templates ├── fulljep.html ├── jepmap.html └── jepsearch.html /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.jar 15 | *.war 16 | *.nar 17 | *.ear 18 | *.zip 19 | *.tar.gz 20 | *.rar 21 | 22 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 23 | hs_err_pid* 24 | 25 | # generated json output 26 | json/*.json 27 | 28 | # generated HTML output 29 | output/*.html 30 | 31 | # IntelliJ files 32 | .idea 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 2-Clause License 2 | 3 | Copyright (c) 2021, Chris Newland 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | 1. Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | 2. Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 17 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 18 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 19 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 20 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 21 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 22 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 23 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 24 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 25 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JEPMap 2 | Maps OpenJDK Projects to their JEPs 3 | 4 | https://chriswhocodes.com/jepmap.html 5 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | groupId 8 | JEPMap 9 | 1.0-SNAPSHOT 10 | 11 | 12 | 11 13 | 11 14 | UTF-8 15 | 16 | 2.1.1 17 | 18 | 19 | 11.0.19 20 | 21 | 22 | 23 | 24 | org.jsoup 25 | jsoup 26 | 1.16.1 27 | 28 | 29 | 30 | 31 | org.json 32 | json 33 | 20231013 34 | 35 | 36 | 37 | jakarta.websocket 38 | jakarta.websocket-api 39 | ${jakarta.websocket.api.version} 40 | 41 | 42 | 43 | org.eclipse.jetty.websocket 44 | websocket-jakarta-server 45 | ${jetty.version} 46 | 47 | 48 | 49 | org.eclipse.jetty.websocket 50 | websocket-jakarta-client 51 | ${jetty.version} 52 | 53 | 54 | 55 | org.eclipse.jetty 56 | jetty-slf4j-impl 57 | ${jetty.version} 58 | 59 | 60 | 61 | 62 | 63 | 64 | org.apache.maven.plugins 65 | maven-shade-plugin 66 | 3.2.4 67 | 68 | true 69 | 70 | 71 | *:* 72 | 73 | META-INF/*.SF 74 | META-INF/*.DSA 75 | META-INF/*.RSA 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | package 84 | 85 | shade 86 | 87 | 88 | 89 | 91 | 93 | 94 | com.chrisnewland.jepmap.JEPProcessor 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | org.codehaus.mojo 105 | exec-maven-plugin 106 | 107 | 108 | default-cli 109 | 110 | java 111 | 112 | 113 | false 114 | com.chrisnewland.jepmap.JEPProcessor 115 | false 116 | compile 117 | 118 | 119 | 120 | 121 | 122 | 123 | -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/JEP.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | 6 | package com.chrisnewland.jepmap; 7 | 8 | import org.json.JSONArray; 9 | import org.json.JSONObject; 10 | 11 | import java.util.*; 12 | 13 | public class JEP 14 | { 15 | private final String name; 16 | private final int number; 17 | private String status; 18 | private String created; 19 | private String updated; 20 | private String release; 21 | private String discussion; 22 | private String issue; 23 | private String body; 24 | 25 | private final Set related = new HashSet<>(); 26 | private final Set depends = new HashSet<>(); 27 | private final Set projectIds = new HashSet<>(); 28 | 29 | public JEP(String name, int number) 30 | { 31 | this.number = number; 32 | 33 | String prefix = "JEP " + number + ":"; 34 | 35 | this.name = name.replace(prefix, "").replace("JEP XXX:", ""); 36 | } 37 | 38 | public String getName() 39 | { 40 | return name; 41 | } 42 | 43 | public int getNumber() 44 | { 45 | return number; 46 | } 47 | 48 | public void addDepends(int number) 49 | { 50 | //System.out.println("JEP " + this.number + " depends on " + number); 51 | depends.add(number); 52 | } 53 | 54 | public Set getDepends() 55 | { 56 | return depends; 57 | } 58 | 59 | public void addRelated(int number) 60 | { 61 | //System.out.println("JEP " + this.number + " related to " + number); 62 | related.add(number); 63 | } 64 | 65 | public Set getRelated() 66 | { 67 | return related; 68 | } 69 | 70 | public String getStatus() 71 | { 72 | return status; 73 | } 74 | 75 | public void setStatus(String status) 76 | { 77 | this.status = status; 78 | } 79 | 80 | public String getCreated() 81 | { 82 | return created; 83 | } 84 | 85 | public void setCreated(String created) 86 | { 87 | this.created = created; 88 | } 89 | 90 | public String getUpdated() 91 | { 92 | return updated; 93 | } 94 | 95 | public void setUpdated(String updated) 96 | { 97 | this.updated = updated; 98 | } 99 | 100 | public String getRelease() 101 | { 102 | return release; 103 | } 104 | 105 | public void setRelease(String release) 106 | { 107 | this.release = release; 108 | } 109 | 110 | public String getDiscussion() 111 | { 112 | return discussion; 113 | } 114 | 115 | public void setDiscussion(String discussion) 116 | { 117 | this.discussion = discussion; 118 | } 119 | 120 | public void addProjectId(String projectId) 121 | { 122 | projectIds.add(projectId); 123 | } 124 | 125 | public void removeProjectId(String projectId) 126 | { 127 | projectIds.remove(projectId); 128 | } 129 | 130 | public void setIssue(String issue) 131 | { 132 | this.issue = issue; 133 | } 134 | 135 | public String getIssue() 136 | { 137 | return issue; 138 | } 139 | 140 | public Set getProjectIds() 141 | { 142 | return projectIds; 143 | } 144 | 145 | public String getBody() 146 | { 147 | return body; 148 | } 149 | 150 | public void setBody(String body) 151 | { 152 | this.body = body; 153 | } 154 | 155 | @Override public String toString() 156 | { 157 | return number + " => " + name + " (status: " + status + ")"; 158 | } 159 | 160 | @Override public boolean equals(Object o) 161 | { 162 | if (this == o) 163 | return true; 164 | if (o == null || getClass() != o.getClass()) 165 | return false; 166 | JEP jep = (JEP) o; 167 | return number == jep.number && name.equals(jep.name); 168 | } 169 | 170 | @Override public int hashCode() 171 | { 172 | return Objects.hash(name, number); 173 | } 174 | 175 | public String toHtmlValueRow() 176 | { 177 | String bugLink = ""; 178 | 179 | if (issue != null) 180 | { 181 | bugLink = makeLink("https://bugs.openjdk.java.net/browse/JDK-" + issue, issue); 182 | } 183 | 184 | StringBuilder builder = new StringBuilder(); 185 | 186 | builder.append(""); 187 | builder.append("").append(makeJEPLink(number, number)).append(""); 188 | builder.append("").append(makeJEPLink(number, getValueOrEmpty(name))).append(""); 189 | builder.append("").append(bugLink).append(""); 190 | builder.append("").append(getValueOrEmpty(status)).append(""); 191 | builder.append("").append(getValueOrEmpty(created).substring(0, 10)).append(""); 192 | builder.append("").append(getValueOrEmpty(updated).substring(0, 10)).append(""); 193 | builder.append("").append(getValueOrEmpty(release)).append(""); 194 | builder.append("").append(getSafeEmail(getValueOrEmpty(discussion))).append(""); 195 | 196 | builder.append("").append(setToString(related, "#")).append(""); 197 | builder.append("").append(setToString(depends, "#")).append(""); 198 | builder.append("").append(setToString(projectIds, "jepmap.html#")).append(""); 199 | 200 | builder.append(""); 201 | return builder.toString(); 202 | } 203 | 204 | private String makeLink(String url, String text) 205 | { 206 | return "" + text + ""; 207 | } 208 | 209 | private String makeJEPLink(int number, Object text) 210 | { 211 | return makeLink(JEPProcessor.URL_JEPS + number, text.toString()); 212 | } 213 | 214 | private String getSafeEmail(String listName) 215 | { 216 | int atPos = listName.indexOf(" at "); 217 | 218 | if (atPos != -1) 219 | { 220 | listName = listName.substring(0, atPos).replace("dash", "-").replace(" ", ""); 221 | } 222 | 223 | return listName; 224 | } 225 | 226 | private String getValueOrEmpty(String str) 227 | { 228 | return (str == null) ? "" : str; 229 | } 230 | 231 | private String setToString(Set set, String linkPrefix) 232 | { 233 | StringBuilder builder = new StringBuilder(); 234 | 235 | for (Object obj : set) 236 | { 237 | String link = makeLink(linkPrefix + obj, obj.toString()); 238 | 239 | builder.append(link).append(", "); 240 | } 241 | 242 | if (builder.length() > 2) 243 | { 244 | builder.delete(builder.length() - 2, builder.length() - 1); 245 | } 246 | 247 | return builder.toString(); 248 | } 249 | 250 | public String serialise() 251 | { 252 | JSONObject jsonObject = new JSONObject(); 253 | 254 | jsonObject.put("name", name); 255 | jsonObject.put("number", number); 256 | jsonObject.put("status", status); 257 | jsonObject.put("created", created); 258 | jsonObject.put("updated", updated); 259 | jsonObject.put("release", release); 260 | jsonObject.put("discussion", discussion); 261 | jsonObject.put("issue", issue); 262 | jsonObject.put("body", body); 263 | 264 | jsonObject.put("related", related); 265 | jsonObject.put("depends", depends); 266 | jsonObject.put("projectIds", projectIds); 267 | 268 | return jsonObject.toString(); 269 | } 270 | 271 | public static JEP deserialise(JSONObject jsonObject) 272 | { 273 | int number = jsonObject.getInt("number"); 274 | 275 | String name = jsonObject.getString("name"); 276 | 277 | JEP jep = new JEP(name, number); 278 | 279 | jep.setStatus(jsonObject.optString("status", null)); 280 | jep.setCreated(jsonObject.optString("created", null)); 281 | jep.setUpdated(jsonObject.optString("updated", null)); 282 | jep.setRelease(jsonObject.optString("release", null)); 283 | jep.setDiscussion(jsonObject.optString("discussion", null)); 284 | jep.setIssue(jsonObject.optString("issue", null)); 285 | jep.setBody(jsonObject.optString("body", null)); 286 | 287 | JSONArray related = jsonObject.optJSONArray("related"); 288 | 289 | if (related != null) 290 | { 291 | for (int i = 0; i < related.length(); i++) 292 | { 293 | jep.addRelated(related.getInt(i)); 294 | } 295 | } 296 | 297 | JSONArray depends = jsonObject.optJSONArray("depends"); 298 | 299 | if (depends != null) 300 | { 301 | for (int i = 0; i < depends.length(); i++) 302 | { 303 | jep.addDepends(depends.getInt(i)); 304 | } 305 | } 306 | 307 | JSONArray projectIds = jsonObject.optJSONArray("projectIds"); 308 | 309 | if (related != null) 310 | { 311 | for (int i = 0; i < projectIds.length(); i++) 312 | { 313 | jep.addProjectId(projectIds.getString(i)); 314 | } 315 | } 316 | 317 | return jep; 318 | } 319 | } -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/JEPMap.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | 6 | package com.chrisnewland.jepmap; 7 | 8 | import java.util.TreeMap; 9 | 10 | public class JEPMap extends TreeMap 11 | { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/JEPProcessor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | 6 | package com.chrisnewland.jepmap; 7 | 8 | import org.jsoup.*; 9 | import org.jsoup.nodes.Document; 10 | import org.jsoup.nodes.Element; 11 | import org.jsoup.select.Elements; 12 | 13 | import java.io.File; 14 | import java.io.FileReader; 15 | import java.io.IOException; 16 | import java.nio.charset.StandardCharsets; 17 | import java.nio.file.Files; 18 | import java.nio.file.Path; 19 | import java.nio.file.Paths; 20 | 21 | import java.time.LocalDateTime; 22 | import java.time.format.DateTimeFormatter; 23 | import java.util.*; 24 | 25 | public class JEPProcessor 26 | { 27 | private static class JEPComparator implements Comparator 28 | { 29 | @Override public int compare(JEP j1, JEP j2) 30 | { 31 | return Integer.compare(j1.getNumber(), j2.getNumber()); 32 | } 33 | } 34 | 35 | private static final String URL_OPENJDK_ROOT = "https://openjdk.java.net/"; 36 | 37 | public static final String URL_JEPS = "https://openjdk.java.net/jeps/"; 38 | 39 | private static final String URL_PROJECT = "https://openjdk.java.net/projects/"; 40 | 41 | private static final String URL_WIKI = "https://wiki.openjdk.java.net/display/"; 42 | 43 | private final Path htmlCachePath = Paths.get("/tmp/jepmap"); 44 | 45 | private final JEPMap jepMap = new JEPMap(); 46 | 47 | private final Map projectMap = new HashMap<>(); 48 | 49 | private final Map> badMappings = new HashMap<>(); 50 | 51 | private final Path pathOutputJson; 52 | 53 | private final Path pathOutputHtml; 54 | 55 | public static void main(String[] args) throws IOException 56 | { 57 | if (args.length != 2) 58 | { 59 | System.err.println("JEPProcessor "); 60 | System.exit(-1); 61 | } 62 | 63 | JEPProcessor jepProcessor = new JEPProcessor(args[0], args[1]); 64 | 65 | jepProcessor.loadBadMappings(); 66 | 67 | jepProcessor.parseJEPs(); 68 | 69 | jepProcessor.parseProjects(); 70 | 71 | jepProcessor.parseProjectsJDK(); 72 | 73 | jepProcessor.associateJEPsToProjects(); 74 | 75 | jepProcessor.cleanBadMappings(); 76 | 77 | jepProcessor.report(); 78 | 79 | jepProcessor.generateJepSearch(); 80 | 81 | jepProcessor.generateFullJep(); 82 | } 83 | 84 | public JEPProcessor(String jsonOutputDir, String htmlOutputDir) 85 | { 86 | this.pathOutputJson = Paths.get(jsonOutputDir); 87 | 88 | this.pathOutputHtml = Paths.get(htmlOutputDir); 89 | 90 | if (!Files.exists(htmlCachePath)) 91 | { 92 | boolean created = htmlCachePath.toFile().mkdirs(); 93 | 94 | if (!created) 95 | { 96 | throw new RuntimeException("Could not create tmp dir " + htmlCachePath); 97 | } 98 | } 99 | } 100 | 101 | private void loadBadMappings() 102 | { 103 | Properties properties = new Properties(); 104 | 105 | Path badMappingsPath = Paths.get("src/main/resources/badmappings.properties"); 106 | 107 | try 108 | { 109 | properties.load(new FileReader(badMappingsPath.toFile())); 110 | 111 | for (String jepNumber : properties.stringPropertyNames()) 112 | { 113 | String badProjectIds = properties.getProperty(jepNumber); 114 | 115 | String[] idArray = badProjectIds.split(","); 116 | 117 | Set idSet = new HashSet<>(Arrays.asList(idArray)); 118 | 119 | System.out.println("JEP " + jepNumber + " must not map to " + Arrays.toString(idArray)); 120 | 121 | badMappings.put(Integer.parseInt(jepNumber), idSet); 122 | } 123 | } 124 | catch (IOException e) 125 | { 126 | throw new RuntimeException("Couldn't load mappings: " + badMappingsPath); 127 | } 128 | } 129 | 130 | private boolean isBapMapping(int jepNumber, String projectId) 131 | { 132 | boolean result = false; 133 | 134 | if (badMappings.containsKey(jepNumber)) 135 | { 136 | Set projectIdSet = badMappings.get(jepNumber); 137 | 138 | if (projectIdSet.contains(projectId) || projectIdSet.contains("*")) 139 | { 140 | result = true; 141 | } 142 | } 143 | 144 | //System.out.println("isBadMapping: " + jepNumber + "=>" + projectId + " : " + result); 145 | 146 | return result; 147 | } 148 | 149 | private void parseProjects() throws IOException 150 | { 151 | System.out.println("parseProjects()"); 152 | 153 | Document doc = loadHTML(URL_OPENJDK_ROOT); 154 | 155 | Element leftSidebar = doc.select("div[id=sidebar]").first(); 156 | 157 | Elements hrefElements = leftSidebar.select("a[href]"); 158 | 159 | System.out.println("hrefElements:" + hrefElements.size()); 160 | 161 | for (Element href : hrefElements) 162 | { 163 | String link = href.attr("href"); 164 | String projectName = href.text(); 165 | 166 | if (linkIsProject(link)) 167 | { 168 | //System.out.println(link + "=>" + projectName); 169 | 170 | String projectId = link.substring("/projects/".length()).toLowerCase(); 171 | 172 | if (!projectId.isEmpty()) 173 | { 174 | Project project = new Project(projectId, projectName); 175 | 176 | projectMap.put(projectId, project); 177 | 178 | try 179 | { 180 | String urlProject = URL_PROJECT + projectId; 181 | String urlWiki = URL_WIKI + projectId; 182 | 183 | parseProject(project, urlProject, true); 184 | project.setProjectURL(urlProject); 185 | 186 | parseProject(project, urlWiki, false); 187 | project.setWikiURL(urlWiki); 188 | } 189 | catch (Exception e) 190 | { 191 | System.out.println("Couldn't parse " + projectId); 192 | } 193 | } 194 | } 195 | } 196 | } 197 | 198 | private String getProjectIdForJDK(int jdk) 199 | { 200 | return "jdk" + (jdk >= 10 ? "/" : "") + jdk; 201 | } 202 | 203 | private int getJDKMajorVersionFromReleaseVersion(String releaseVersion) 204 | { 205 | int lastMajorChar; 206 | 207 | for (lastMajorChar = 0; lastMajorChar < releaseVersion.length(); lastMajorChar++) 208 | { 209 | char c = releaseVersion.charAt(lastMajorChar); 210 | 211 | if (!Character.isDigit(c)) 212 | { 213 | break; 214 | } 215 | } 216 | 217 | System.out.println("Substring 0-" + lastMajorChar + " from " + releaseVersion); 218 | 219 | int result = Integer.parseInt(releaseVersion.substring(0, lastMajorChar)); 220 | 221 | System.out.println("Got major version " + result + " from " + releaseVersion); 222 | 223 | return result; 224 | } 225 | 226 | private void parseProjectsJDK() 227 | { 228 | System.out.println("parseProjectsJDK()"); 229 | 230 | int min = 6; 231 | 232 | int max = 24; 233 | 234 | for (int jdk = min; jdk <= max; jdk++) 235 | { 236 | String projectName = "JDK" + jdk; 237 | 238 | String projectId = getProjectIdForJDK(jdk); 239 | 240 | Project project = new Project(projectId, projectName); 241 | 242 | projectMap.put(projectId, project); 243 | 244 | try 245 | { 246 | String urlProject = URL_PROJECT + projectId; 247 | String urlWiki = URL_WIKI + projectId; 248 | 249 | parseProject(project, urlProject, true); 250 | project.setProjectURL(urlProject); 251 | 252 | parseProject(project, urlWiki, false); 253 | project.setWikiURL(urlWiki); 254 | } 255 | catch (Exception e) 256 | { 257 | System.out.println("Couldn't parse " + projectId); 258 | e.printStackTrace(); 259 | } 260 | } 261 | } 262 | 263 | private void cleanBadMappings() 264 | { 265 | for (Project project : projectMap.values()) 266 | { 267 | Set projectJEPs = project.getJeps(); 268 | 269 | Iterator iterator = projectJEPs.iterator(); 270 | 271 | System.out.println(project.getId() + " before clean: " + projectJEPs.size()); 272 | 273 | while (iterator.hasNext()) 274 | { 275 | JEP jep = iterator.next(); 276 | 277 | if (isBapMapping(jep.getNumber(), project.getId())) 278 | { 279 | System.out.println("Removing bad mapping JEP: " + jep.getNumber() + " Project: " + project.getId()); 280 | iterator.remove(); 281 | 282 | jep.removeProjectId(project.getId()); 283 | } 284 | } 285 | 286 | System.out.println(project.getId() + " after clean: " + projectJEPs.size()); 287 | 288 | } 289 | } 290 | 291 | private void report() throws IOException 292 | { 293 | List projectList = new ArrayList<>(projectMap.values()); 294 | 295 | projectList.sort(new Comparator() 296 | { 297 | @Override public int compare(Project p1, Project p2) 298 | { 299 | String s1 = p1.getName(); 300 | String s2 = p2.getName(); 301 | 302 | //sSystem.out.println("comp " + s1 + " v " + s2); 303 | 304 | if (s1.startsWith("JDK") && s2.startsWith("JDK")) 305 | { 306 | s1 = s1.substring(3); 307 | s2 = s2.substring(3); 308 | 309 | return Integer.compare(Integer.parseInt(s1), Integer.parseInt(s2)); 310 | } 311 | else 312 | { 313 | return s1.compareTo(s2); 314 | } 315 | 316 | } 317 | }); 318 | 319 | StringBuilder builderJump = new StringBuilder(); 320 | 321 | StringBuilder builderProject = new StringBuilder(); 322 | 323 | for (Project project : projectList) 324 | { 325 | Set projectJEPs = project.getJeps(); 326 | 327 | if (!projectJEPs.isEmpty()) 328 | { 329 | System.out.println("------------------------------PROJECT: " + project.getName() + " (" + project.getId() + ")"); 330 | 331 | builderJump.append("\n"); 336 | 337 | builderProject.append("
\n"); 338 | builderProject.append("

") 342 | .append(project.getName()) 343 | .append("

\n"); 344 | 345 | builderProject.append("
").append(project.getDescription()); 346 | 347 | if (project.getDescription() != null) 348 | { 349 | if (project.getDescription().toLowerCase().contains("wiki")) 350 | { 351 | builderProject.append(" (") 354 | .append(project.getWikiURL()) 355 | .append(")"); 356 | } 357 | } 358 | else 359 | { 360 | System.out.println("WARN: missing description on project " + project.getId()); 361 | } 362 | 363 | builderProject.append("
\n"); 364 | 365 | List jepList = new ArrayList<>(project.getJeps()); 366 | 367 | jepList.sort(new JEPComparator()); 368 | 369 | builderProject.append("

JEPs

\n"); 370 | 371 | builderProject.append("
\n"); 372 | 373 | for (JEP jep : jepList) 374 | { 375 | builderProject.append("
JEP ") 379 | .append(jep.getNumber()) 380 | .append(": ") 381 | .append(jep.getName()) 382 | .append("
"); 383 | if (jep.getRelease() != null) 384 | { 385 | builderProject.append("[Release: ").append(jep.getRelease()).append("] "); 386 | } 387 | builderProject.append("[Status: ").append(jep.getStatus()); 388 | builderProject.append("] [Updated: ").append(jep.getUpdated().substring(0, 10)); 389 | 390 | builderProject.append("]
\n"); 391 | 392 | System.out.println(jep); 393 | } 394 | 395 | builderProject.append("
\n"); 396 | 397 | builderProject.append("
\n"); 398 | } 399 | } 400 | 401 | String template = getResource("templates/jepmap.html"); 402 | 403 | template = template.replace("%TOPMENU%", getResource("menu.html")); 404 | 405 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE; 406 | 407 | template = template.replace("%UPDATED%", dateTimeFormatter.format(LocalDateTime.now())); 408 | 409 | template = template.replace("%JUMP%", builderJump.toString()); 410 | 411 | template = template.replace("%BODY%", builderProject.toString()); 412 | 413 | Files.write(pathOutputHtml.resolve("jepmap.html"), template.getBytes(StandardCharsets.UTF_8)); 414 | } 415 | 416 | private void generateJepSearch() throws IOException 417 | { 418 | StringBuilder builder = new StringBuilder(); 419 | 420 | List jepList = new ArrayList<>(jepMap.values()); 421 | 422 | jepList.sort(new JEPComparator()); 423 | 424 | for (JEP jep : jepList) 425 | { 426 | builder.append(jep.toHtmlValueRow()).append("\n"); 427 | } 428 | 429 | String template = getResource("templates/jepsearch.html"); 430 | 431 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE; 432 | 433 | template = template.replace("%UPDATED%", dateTimeFormatter.format(LocalDateTime.now())); 434 | 435 | template = template.replace("%TOPMENU%", getResource("menu.html")); 436 | 437 | template = template.replace("%BODY%", builder.toString()); 438 | 439 | Files.write(pathOutputHtml.resolve("jepsearch.html"), template.getBytes(StandardCharsets.UTF_8)); 440 | } 441 | 442 | private void generateFullJep() throws IOException 443 | { 444 | StringBuilder builder = new StringBuilder(); 445 | 446 | List jepList = new ArrayList<>(jepMap.values()); 447 | 448 | jepList.sort(new JEPComparator()); 449 | 450 | for (JEP jep : jepList) 451 | { 452 | builder.append(jep.toHtmlValueRow()).append("\n"); 453 | } 454 | 455 | String template = getResource("templates/fulljep.html"); 456 | 457 | DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ISO_DATE; 458 | 459 | template = template.replace("%UPDATED%", dateTimeFormatter.format(LocalDateTime.now())); 460 | 461 | template = template.replace("%TOPMENU%", getResource("menu.html")); 462 | 463 | template = template.replace("%BODY%", builder.toString()); 464 | 465 | Files.write(pathOutputHtml.resolve("fulljep.html"), template.getBytes(StandardCharsets.UTF_8)); 466 | } 467 | 468 | private String getResource(String filename) throws IOException 469 | { 470 | return Files.readString(Paths.get("src/main/resources/", filename), StandardCharsets.UTF_8); 471 | } 472 | 473 | private Document loadHTML(String url) throws IOException 474 | { 475 | String filename = url.replace(":", "-").replace("/", "_"); 476 | 477 | File file = new File(htmlCachePath.toFile(), filename); 478 | 479 | Document document; 480 | 481 | if (file.exists()) 482 | { 483 | //System.out.println("Loading from file: " + file.getAbsolutePath()); 484 | document = Jsoup.parse(file, "UTF-8", url); 485 | } 486 | else 487 | { 488 | //System.out.println("No file: " + file.getAbsolutePath()); 489 | 490 | try 491 | { 492 | System.out.println("Fetching from network: " + url); 493 | 494 | document = Jsoup.connect(url).userAgent("JEPMap - https://github.com/chriswhocodes/JEPMap").followRedirects(true).get(); 495 | } 496 | catch (Exception e) 497 | { 498 | //System.out.println("Writing empty file: " + file.getAbsolutePath()); 499 | 500 | Files.write(file.toPath(), new byte[0]); 501 | throw e; 502 | } 503 | 504 | String htmlToSave = document.outerHtml().replace(" ", " ").replace(" ", " "); 505 | 506 | //System.out.println("Saving to file: " + file.getAbsolutePath()); 507 | Files.write(file.toPath(), htmlToSave.getBytes(StandardCharsets.UTF_8)); 508 | } 509 | 510 | return document; 511 | } 512 | 513 | private void parseProject(Project project, String url, boolean parseDescription) throws IOException 514 | { 515 | System.out.println("parseProject(" + url + ")"); 516 | 517 | Document doc = loadHTML(url); 518 | 519 | if (parseDescription) 520 | { 521 | String description = doc.selectFirst("p").text(); 522 | 523 | if (description.endsWith(":")) 524 | { 525 | String ulBlock = doc.selectFirst("ul").html().replace("

", "").replace("

", "").replace("
", ""); 526 | 527 | description += "
    " + ulBlock + "
"; 528 | } 529 | 530 | project.setDescription(description); 531 | } 532 | 533 | Elements hrefElements = doc.select("a[href]"); 534 | 535 | for (Element href : hrefElements) 536 | { 537 | String link = href.attr("href"); 538 | 539 | if (linkIsJEP(link)) 540 | { 541 | int jepNumber = getNumberFromJEPLink(link); 542 | 543 | System.out.println("Got JEP number " + jepNumber + " from " + link); 544 | 545 | if (jepNumber == 8294992 || jepNumber == 8277163) 546 | { 547 | jepNumber = 450; 548 | } 549 | 550 | JEP jep = jepMap.get(jepNumber); 551 | 552 | if (jep != null) 553 | { 554 | project.addJEP(jep); 555 | } 556 | else 557 | { 558 | System.out.println("Error, no JEP found for " + jepNumber); 559 | } 560 | } 561 | } 562 | } 563 | 564 | private int getNumberFromJEPLink(String link) 565 | { 566 | //System.out.println("getNumberFromJEPLink: " + link); 567 | 568 | String[] parts = link.split("/"); 569 | 570 | String last = parts[parts.length - 1]; 571 | 572 | if (last.indexOf('#') != -1) 573 | { 574 | last = last.substring(0, last.indexOf('#')); 575 | } 576 | 577 | return Integer.parseInt(last); 578 | } 579 | 580 | private String getProjectIdFromLink(String link) 581 | { 582 | System.out.println("getProjectIdFromLink: " + link); 583 | 584 | link = link.replace("%5D", ""); 585 | 586 | String[] parts = link.split("/"); 587 | 588 | boolean lastPartWasProjects = false; 589 | 590 | String projectId = null; 591 | 592 | for (String part : parts) 593 | { 594 | if ("projects".equals(part)) 595 | { 596 | lastPartWasProjects = true; 597 | continue; 598 | } 599 | 600 | if (lastPartWasProjects) 601 | { 602 | projectId = part; 603 | break; 604 | } 605 | } 606 | 607 | if (projectId != null && projectId.indexOf('#') != -1) 608 | { 609 | projectId = projectId.substring(0, projectId.indexOf('#')); 610 | } 611 | 612 | System.out.println("getProjectIdFromLink got: " + projectId); 613 | 614 | return projectId; 615 | } 616 | 617 | private void parseJEPs() throws IOException 618 | { 619 | Document documentJEPs = loadHTML(URL_JEPS); 620 | 621 | Elements jepTables = documentJEPs.select("table[class=jeps]"); 622 | 623 | for (Element jepTable : jepTables) 624 | { 625 | Elements hrefElements = jepTable.select("a[href]"); 626 | 627 | for (Element hrefElement : hrefElements) 628 | { 629 | String link = hrefElement.attr("href"); 630 | 631 | System.out.println("JEP link: " + link); 632 | 633 | try 634 | { 635 | int jepNumber = Integer.parseInt(link); 636 | 637 | System.out.println("looking for " + link); 638 | 639 | JEP jep = parseJEP(jepNumber); 640 | 641 | jepMap.put(jepNumber, jep); 642 | } 643 | catch (Exception e) 644 | { 645 | System.out.println("Couldn't load JEP " + link); 646 | 647 | e.printStackTrace(); 648 | System.exit(-1); 649 | } 650 | } 651 | } 652 | } 653 | 654 | private JEP parseJEP(int number) throws IOException 655 | { 656 | String url = URL_JEPS + number; 657 | 658 | Document doc = loadHTML(url); 659 | 660 | Element h1 = doc.select("h1").first(); 661 | 662 | String title = h1.text(); 663 | 664 | JEP jep = new JEP(title, number); 665 | 666 | System.out.println("================================ " + jep); 667 | 668 | Element headTable = doc.select("table.head").first(); 669 | 670 | Elements trElements = headTable.getElementsByTag("tr"); 671 | 672 | boolean inRelated = false; 673 | boolean inDepends = false; 674 | 675 | for (Element tr : trElements) 676 | { 677 | Elements tdElements = tr.getElementsByTag("td"); 678 | 679 | if (tdElements.size() == 2) 680 | { 681 | String key = tdElements.get(0).text(); 682 | 683 | Element valueElementTd = tdElements.get(1); 684 | String valueText = valueElementTd.text(); 685 | 686 | System.out.println(key + "=>" + valueText); 687 | 688 | if ("Relates to".equals(key)) 689 | { 690 | inRelated = true; 691 | } 692 | else if (!key.trim().isEmpty()) 693 | { 694 | inRelated = false; 695 | } 696 | 697 | if ("Depends".equals(key)) 698 | { 699 | inDepends = true; 700 | } 701 | else if (!key.trim().isEmpty()) 702 | { 703 | inDepends = false; 704 | } 705 | 706 | if (inRelated) 707 | { 708 | String hrefJEP = valueElementTd.child(0).attr("href"); 709 | 710 | try 711 | { 712 | int related = Integer.parseInt(hrefJEP); 713 | 714 | jep.addRelated(related); 715 | } 716 | catch (NumberFormatException nfe) 717 | { 718 | } 719 | } 720 | else if (inDepends) 721 | { 722 | String hrefJEP = valueElementTd.child(0).attr("href"); 723 | 724 | try 725 | { 726 | int depends = Integer.parseInt(hrefJEP); 727 | 728 | jep.addDepends(depends); 729 | } 730 | catch (NumberFormatException nfe) 731 | { 732 | } 733 | } 734 | 735 | if ("Discussion".equals(key)) 736 | { 737 | jep.setDiscussion(valueText); 738 | } 739 | else if ("Status".equals(key)) 740 | { 741 | jep.setStatus(valueText); 742 | } 743 | else if ("Created".equals(key)) 744 | { 745 | jep.setCreated(valueText); 746 | } 747 | else if ("Updated".equals(key)) 748 | { 749 | jep.setUpdated(valueText); 750 | } 751 | else if ("Release".equals(key)) 752 | { 753 | jep.setRelease(valueText); 754 | } 755 | else if ("Issue".equals(key)) 756 | { 757 | jep.setIssue(valueText); 758 | } 759 | } 760 | } 761 | 762 | Element markdown = doc.select("div[class=markdown]").first(); 763 | 764 | jep.setBody(markdown.text()); 765 | 766 | Elements hrefElements = markdown.select("a[href]"); 767 | 768 | for (Element hrefElement : hrefElements) 769 | { 770 | String link = hrefElement.attr("href"); 771 | 772 | if (linkIsProject(link)) 773 | { 774 | String projectId = getProjectIdFromLink(link); 775 | 776 | System.out.println("Found project link in JEP:" + projectId); 777 | 778 | jep.addProjectId(projectId); 779 | } 780 | } 781 | 782 | Files.write(pathOutputJson.resolve(jep.getNumber() + ".json"), jep.serialise().getBytes(StandardCharsets.UTF_8)); 783 | 784 | return jep; 785 | } 786 | 787 | private void associateJEPsToProjects() 788 | { 789 | for (JEP jep : jepMap.values()) 790 | { 791 | String discussion = jep.getDiscussion(); 792 | 793 | //System.out.println("Checking discussion " + discussion + " for " + jep); 794 | 795 | if (discussion != null) 796 | { 797 | discussion = discussion.replace(" dash ", "-").replace(" at ", "@").replace(" dot ", "."); 798 | 799 | discussion = discussion.replace("-dev", ""); 800 | 801 | int atPos = discussion.indexOf('@'); 802 | 803 | if (atPos != -1) 804 | { 805 | String projectId = discussion.substring(0, atPos); 806 | 807 | System.out.println(jep + " discussed on " + projectId); 808 | 809 | if (projectMap.containsKey(projectId)) 810 | { 811 | Project project = projectMap.get(projectId); 812 | 813 | if (project != null) 814 | { 815 | project.addJEP(jep); 816 | } 817 | } 818 | } 819 | } 820 | 821 | String release = jep.getRelease(); 822 | 823 | if (release != null && !release.isEmpty() && !"tbd".equals(release)) 824 | { 825 | int jdkMajorVersion = getJDKMajorVersionFromReleaseVersion(release); 826 | 827 | String projectId = getProjectIdForJDK(jdkMajorVersion); 828 | 829 | System.out.println(jep + " released in " + projectId); 830 | 831 | if (projectMap.containsKey(projectId)) 832 | { 833 | Project project = projectMap.get(projectId); 834 | 835 | if (project != null) 836 | { 837 | project.addJEP(jep); 838 | } 839 | } 840 | } 841 | 842 | Set projectIds = jep.getProjectIds(); 843 | 844 | for (String projectId : projectIds) 845 | { 846 | System.out.println("Checking projectId " + projectId + " for " + jep); 847 | 848 | if (projectMap.containsKey(projectId)) 849 | { 850 | Project project = projectMap.get(projectId); 851 | 852 | System.out.println("Found project: " + project.getName()); 853 | 854 | if (project != null) 855 | { 856 | project.addJEP(jep); 857 | } 858 | } 859 | } 860 | } 861 | } 862 | 863 | private boolean linkIsProject(String url) 864 | { 865 | if (url.contains("http") && !url.contains("openjdk.java.net")) 866 | { 867 | System.out.println("Ignoring non-JDK project URL " + url); 868 | return false; 869 | } 870 | 871 | return url.contains("/projects/") && !"/projects/".equals(url) && !url.contains("/projects/jdk"); 872 | } 873 | 874 | private boolean linkIsJEP(String url) 875 | { 876 | return url.contains("/jeps/") && !"/jeps/".equals(url) && !url.endsWith("/jeps/0"); 877 | } 878 | } 879 | -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/Project.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | 6 | package com.chrisnewland.jepmap; 7 | 8 | import java.util.HashSet; 9 | import java.util.Set; 10 | 11 | public class Project 12 | { 13 | private final Set jepSet = new HashSet<>(); 14 | 15 | private final String id; 16 | 17 | private final String name; 18 | 19 | private String description; 20 | 21 | private String projectURL; 22 | 23 | private String wikiURL; 24 | 25 | public Project(String id, String name) 26 | { 27 | this.id = id; 28 | this.name = name; 29 | } 30 | 31 | public void addJEP(JEP jep) 32 | { 33 | if (jep == null) 34 | { 35 | Thread.dumpStack(); 36 | System.exit(-1); 37 | } 38 | 39 | if (!jepSet.contains(jep)) 40 | { 41 | jepSet.add(jep); 42 | System.out.println("Added " + jep.getNumber() + " to " + name); 43 | } 44 | } 45 | 46 | public Set getJeps() 47 | { 48 | return jepSet; 49 | } 50 | 51 | public String getName() 52 | { 53 | return name; 54 | } 55 | 56 | public String getId() 57 | { 58 | return id; 59 | } 60 | 61 | public String getProjectURL() 62 | { 63 | return projectURL; 64 | } 65 | 66 | public void setProjectURL(String projectURL) 67 | { 68 | this.projectURL = projectURL; 69 | } 70 | 71 | public String getWikiURL() 72 | { 73 | return wikiURL; 74 | } 75 | 76 | public void setWikiURL(String wikiURL) 77 | { 78 | this.wikiURL = wikiURL; 79 | } 80 | 81 | public String getDescription() 82 | { 83 | return description; 84 | } 85 | 86 | public void setDescription(String description) 87 | { 88 | this.description = description; 89 | } 90 | } -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/websocket/FullJEPServer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | package com.chrisnewland.jepmap.websocket; 6 | 7 | import org.eclipse.jetty.server.*; 8 | import org.eclipse.jetty.servlet.ServletContextHandler; 9 | import org.eclipse.jetty.websocket.jakarta.server.config.JakartaWebSocketServletContainerInitializer; 10 | 11 | import java.net.InetSocketAddress; 12 | import java.nio.file.Path; 13 | import java.nio.file.Paths; 14 | 15 | public class FullJEPServer 16 | { 17 | public static void main(String[] args) 18 | { 19 | Path jepDir = Paths.get(args[0]); 20 | 21 | new FullJEPServer(jepDir); 22 | } 23 | 24 | private static JEPLoader jepLoader; 25 | 26 | public static JEPLoader getJEPLoader() 27 | { 28 | return jepLoader; 29 | } 30 | 31 | public FullJEPServer(Path jepDir) 32 | { 33 | jepLoader = new JEPLoader(jepDir); 34 | 35 | Server server = new Server(new InetSocketAddress("127.0.0.1", 8080)); 36 | 37 | ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); 38 | 39 | context.setContextPath("/"); 40 | 41 | server.setHandler(context); 42 | 43 | JakartaWebSocketServletContainerInitializer.configure(context, (servletContext, wsContainer) -> { 44 | wsContainer.setDefaultMaxTextMessageBufferSize(256); 45 | wsContainer.setDefaultMaxBinaryMessageBufferSize(256); 46 | wsContainer.setDefaultMaxSessionIdleTimeout(120_000); 47 | wsContainer.addEndpoint(WebsocketServerEndpoint.class); 48 | }); 49 | 50 | try 51 | { 52 | server.start(); 53 | 54 | server.join(); 55 | } 56 | catch (Exception e) 57 | { 58 | e.printStackTrace(); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/websocket/JEPLoader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | package com.chrisnewland.jepmap.websocket; 6 | 7 | import com.chrisnewland.jepmap.JEP; 8 | import org.json.JSONObject; 9 | 10 | import java.io.File; 11 | import java.io.FilenameFilter; 12 | import java.io.IOException; 13 | import java.nio.file.Files; 14 | import java.nio.file.Path; 15 | import java.util.ArrayList; 16 | import java.util.Comparator; 17 | import java.util.List; 18 | 19 | public class JEPLoader 20 | { 21 | private static final String SUFFIX = ".json"; 22 | 23 | private final List jepList; 24 | 25 | public JEPLoader(Path jepDir) 26 | { 27 | File[] jepFiles = jepDir.toFile().listFiles(new FilenameFilter() 28 | { 29 | @Override public boolean accept(File dir, String name) 30 | { 31 | return name.endsWith(SUFFIX); 32 | } 33 | }); 34 | 35 | jepList = new ArrayList<>(); 36 | 37 | try 38 | { 39 | for (File jepFile : jepFiles) 40 | { 41 | String contents = Files.readString(jepFile.toPath()); 42 | 43 | JEP jep = JEP.deserialise(new JSONObject(contents)); 44 | 45 | jepList.add(jep); 46 | } 47 | 48 | jepList.sort(new Comparator() 49 | { 50 | @Override public int compare(JEP o1, JEP o2) 51 | { 52 | return Integer.compare(o1.getNumber(), o2.getNumber()); 53 | } 54 | }); 55 | } 56 | catch (IOException ioe) 57 | { 58 | ioe.printStackTrace(); 59 | } 60 | } 61 | 62 | public List searchJEPs(String searchLower) 63 | { 64 | List result = new ArrayList<>(); 65 | 66 | for (JEP jep : jepList) 67 | { 68 | if (jep.getName().toLowerCase().contains(searchLower) || jep.getBody().toLowerCase().contains(searchLower)) 69 | { 70 | result.add(jep); 71 | } 72 | } 73 | 74 | return result; 75 | } 76 | } -------------------------------------------------------------------------------- /src/main/java/com/chrisnewland/jepmap/websocket/WebsocketServerEndpoint.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2021 Chris Newland. 3 | * Licensed under https://github.com/chriswhocodes/JEPMap/blob/master/LICENSE 4 | */ 5 | package com.chrisnewland.jepmap.websocket; 6 | 7 | import com.chrisnewland.jepmap.JEP; 8 | import org.json.JSONArray; 9 | import org.json.JSONObject; 10 | 11 | import java.io.IOException; 12 | import java.util.List; 13 | import jakarta.websocket.CloseReason; 14 | import jakarta.websocket.OnClose; 15 | import jakarta.websocket.OnError; 16 | import jakarta.websocket.OnMessage; 17 | import jakarta.websocket.OnOpen; 18 | import jakarta.websocket.Session; 19 | import jakarta.websocket.server.ServerEndpoint; 20 | 21 | @ServerEndpoint(value = "/fulltext") public class WebsocketServerEndpoint 22 | { 23 | @OnOpen public void onWebSocketConnect(Session session) 24 | { 25 | } 26 | 27 | @OnMessage public void onWebSocketText(Session session, String search) throws IOException 28 | { 29 | if (search.length() >= 3) 30 | { 31 | search = search.toLowerCase(); 32 | 33 | long start = System.currentTimeMillis(); 34 | List jeps = FullJEPServer.getJEPLoader().searchJEPs(search.trim()); 35 | long stop = System.currentTimeMillis(); 36 | 37 | System.out.println(search + " in " + (stop - start) + "ms found " + jeps.size() + " results"); 38 | 39 | JSONArray result = new JSONArray(); 40 | 41 | int context = 80; 42 | 43 | if (!jeps.isEmpty()) 44 | { 45 | forlabel: 46 | for (JEP jep : jeps) 47 | { 48 | try 49 | { 50 | String body = jep.getBody(); 51 | 52 | String bodyLower = body.toLowerCase(); 53 | 54 | StringBuilder builder = new StringBuilder(); 55 | 56 | int pos = bodyLower.indexOf(search); 57 | 58 | builder.append("
    "); 59 | 60 | do 61 | { 62 | String snippet = body.substring(Math.max(0, pos - context), Math.min(pos + context, body.length())); 63 | 64 | int firstSpace = snippet.indexOf(' '); 65 | int lastSpace = snippet.lastIndexOf(' '); 66 | 67 | if (firstSpace == -1 || lastSpace == -1 || firstSpace == lastSpace) 68 | { 69 | continue forlabel; 70 | } 71 | 72 | builder.append("
  • ...").append(snippet.substring(firstSpace + 1, lastSpace)).append("...
  • "); 73 | 74 | pos = bodyLower.indexOf(search, pos + context); 75 | 76 | } while (pos != -1); 77 | 78 | builder.append("
"); 79 | 80 | JSONObject jsonObject = new JSONObject(); 81 | 82 | jsonObject.put("number", jep.getNumber()); 83 | jsonObject.put("name", jep.getName()); 84 | jsonObject.put("snippet", builder); 85 | 86 | result.put(jsonObject); 87 | } 88 | catch (Exception e) 89 | { 90 | e.printStackTrace(); 91 | } 92 | } 93 | } 94 | 95 | session.getBasicRemote().sendText(result.toString()); 96 | } 97 | } 98 | 99 | @OnClose public void onWebSocketClose(CloseReason reason) 100 | { 101 | //System.out.println("Socket Closed: " + reason); 102 | } 103 | 104 | @OnError public void onWebSocketError(Throwable cause) 105 | { 106 | if (cause.getMessage() != null) 107 | { 108 | System.out.println("onWebSocketError: " + cause.getMessage()); 109 | } 110 | } 111 | } -------------------------------------------------------------------------------- /src/main/resources/badmappings.properties: -------------------------------------------------------------------------------- 1 | 0=* 2 | 1=* 3 | 2=* 4 | 3=* 5 | 12=amber 6 | 369=zgc,tsan,valhalla,code-tools,lanai,loom,jmc,metropolis,openjfx,panama,jdk/16,mobile,amber 7 | 126=closures 8 | 217=javadoc-next 9 | 175=zero 10 | 294=zero,ppc-aix-port 11 | 418=loom,panama 12 | 232=code-tools 13 | 357=code-tools 14 | 15 | -------------------------------------------------------------------------------- /src/main/resources/menu.html: -------------------------------------------------------------------------------- 1 | 25 | 73 |
74 | -------------------------------------------------------------------------------- /src/main/resources/style_jepmap.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: sans-serif; 3 | font-size: 14px; 4 | } 5 | 6 | h2 { 7 | margin-top: 4px; 8 | } 9 | 10 | .project, .algo { 11 | border: solid 1px gray; 12 | background-color: lavender; 13 | margin: 16px 0px; 14 | padding: 8px; 15 | } 16 | 17 | .jeps, .jump_container { 18 | border: solid 1px gray; 19 | background-color: khaki; 20 | padding: 8px; 21 | } 22 | 23 | .jep { 24 | padding: 4px 0px 4px 8px; 25 | } 26 | 27 | .jepstatus { 28 | font-size: smaller; 29 | } 30 | 31 | .description { 32 | font-style: italic; 33 | } 34 | 35 | li { 36 | padding: 4px; 37 | } 38 | 39 | .intro > div { 40 | font-weight: bold; 41 | margin-bottom: 8px; 42 | } 43 | 44 | .jump { 45 | border: 1px solid gray; 46 | float: left; 47 | clear: none; 48 | margin: 4px; 49 | padding: 4px 8px; 50 | background-color: lavender; 51 | font-weight: bolder; 52 | } 53 | 54 | .clear { 55 | clear: both; 56 | } 57 | 58 | .warn { 59 | font-style: italic; 60 | color: red; 61 | font-weight: bold; 62 | padding-bottom: 8px; 63 | } 64 | 65 | h1 66 | { 67 | margin: 8px 0px; 68 | } 69 | 70 | .dataTables_filter { 71 | float: left !important; 72 | font-size: 24px; 73 | } 74 | 75 | .dataTables_filter input { 76 | height: 32px; 77 | width: 600px; 78 | font-size: 24px; 79 | border: 2px solid black; 80 | } -------------------------------------------------------------------------------- /src/main/resources/templates/fulljep.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 57 | 133 | 134 | 135 | %TOPMENU% 136 |

FullJEP Search

137 |

(JDK Enhancement Proposal Full-Text Search)

138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 |
WebsocketResponse time (ms)
148 |
149 |
150 | 151 |
152 |
153 |
154 | 155 | -------------------------------------------------------------------------------- /src/main/resources/templates/jepmap.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | OpenJDK Project JEPs 6 | 7 | 8 | 9 | %TOPMENU% 10 |

OpenJDK Project JEPs (JDK Enhancement Proposals)

11 | 12 |
13 |
Built using JEPMap by @chriswhocodes. 15 | Last updated: %UPDATED% 16 |
17 |
18 |
The algorithm for generating this page is: 19 |
    20 |
  1. Parse the JEP pages at https://openjdk.java.net/jeps
  2. 21 |
  3. For each Project listed at https://openjdk.java.net/projects/ 22 |
      23 |
    1. Parse the project page and wiki page looking for JEP URLs
    2. 24 |
    3. Check if the JEP page discussion mailing list name matches a Project
    4. 25 |
    5. Check if the JEP page text links to a Project
    6. 26 |
    7. Map JEP 'Release' tag back to a JDK (technically a JDK is not a Project but I think it's useful to list the 27 | JEPs 28 | targeted to a JDK) 29 |
    8. 30 |
    9. Remove mappings found in the banlist 32 |
    10. 33 |
    34 |
  4. 35 |
36 |
Autogeneration can produce false positives! Please report issues at https://github.com/chriswhocodes/JEPMap/issues
38 |
39 |
40 | %JUMP% 41 |
42 |
43 | %BODY% 44 | 45 | -------------------------------------------------------------------------------- /src/main/resources/templates/jepsearch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sort, Search, and Filter JEPs 6 | 7 | 8 | 10 | 11 | 13 | 15 | 16 | 17 | 77 | %TOPMENU% 78 |

Sort, Search, and Filter JEPs

79 |
80 |
Built using JEPMap by @chriswhocodes. 82 | Last updated: %UPDATED% 83 |
84 |
85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | %BODY% 115 |
NumberNameIssueStatusCreatedUpdatedReleaseDiscussionRelatedDependsProjects
StatusRelease
116 | 117 | --------------------------------------------------------------------------------