29 import java.io.BufferedWriter; |
29 import java.io.BufferedWriter; |
30 import java.io.IOException; |
30 import java.io.IOException; |
31 import java.io.InputStream; |
31 import java.io.InputStream; |
32 import java.io.InputStreamReader; |
32 import java.io.InputStreamReader; |
33 import java.io.PrintWriter; |
33 import java.io.PrintWriter; |
|
34 import java.lang.module.ModuleDescriptor; |
34 import java.lang.module.ModuleFinder; |
35 import java.lang.module.ModuleFinder; |
|
36 import java.lang.module.ModuleReference; |
35 import java.nio.charset.StandardCharsets; |
37 import java.nio.charset.StandardCharsets; |
36 import java.nio.file.Files; |
38 import java.nio.file.Files; |
37 import java.nio.file.Path; |
39 import java.nio.file.Path; |
38 import java.nio.file.Paths; |
40 import java.nio.file.Paths; |
|
41 import java.util.Arrays; |
|
42 import java.util.Comparator; |
39 import java.util.HashMap; |
43 import java.util.HashMap; |
40 import java.util.Locale; |
44 import java.util.Locale; |
41 import java.util.Map; |
45 import java.util.Map; |
42 import java.util.Properties; |
46 import java.util.Properties; |
43 import java.util.Set; |
47 import java.util.Set; |
44 import java.util.stream.Collectors; |
48 import java.util.function.Predicate; |
45 import java.util.stream.Stream; |
49 import java.util.stream.Stream; |
|
50 import static java.util.stream.Collectors.*; |
46 |
51 |
47 /** |
52 /** |
48 * Build tool to generate the docs bundle index page. |
53 * Build tool to generate the docs bundle index page. |
49 */ |
54 */ |
50 public class GenDocsBundlePage { |
55 public class GenDocsBundlePage { |
102 return GenDocsBundlePage.class.getResourceAsStream(DOCS_BUNDLE_PAGE); |
107 return GenDocsBundlePage.class.getResourceAsStream(DOCS_BUNDLE_PAGE); |
103 } |
108 } |
104 } |
109 } |
105 |
110 |
106 private static final String HEADER_TITLE = "@HEADER_TITLE@"; |
111 private static final String HEADER_TITLE = "@HEADER_TITLE@"; |
|
112 |
|
113 |
107 final Path outputfile; |
114 final Path outputfile; |
108 final String title; |
115 final String title; |
109 final Map<String, String> moduleGroups; |
116 final Map<String, Set<ModuleDescriptor>> moduleGroups = new HashMap<>(); |
110 |
|
111 GenDocsBundlePage(String title, Path outputfile) throws IOException |
117 GenDocsBundlePage(String title, Path outputfile) throws IOException |
112 { |
118 { |
113 this.outputfile = outputfile; |
119 this.outputfile = outputfile; |
114 this.title = title; |
120 this.title = title; |
115 this.moduleGroups = moduleGroups(); |
121 |
116 } |
122 // read module groups |
117 |
|
118 static Map<String, String> moduleGroups() throws IOException { |
|
119 ModuleFinder finder = ModuleFinder.ofSystem(); |
123 ModuleFinder finder = ModuleFinder.ofSystem(); |
120 Map<String, String> groups = new HashMap<>(); |
|
121 try (InputStream in = GenDocsBundlePage.class.getResourceAsStream(MODULE_GROUPS_PROPS)) { |
124 try (InputStream in = GenDocsBundlePage.class.getResourceAsStream(MODULE_GROUPS_PROPS)) { |
122 Properties props = new Properties(); |
125 Properties props = new Properties(); |
123 props.load(in); |
126 props.load(in); |
124 for (String key: props.stringPropertyNames()) { |
127 for (String key: props.stringPropertyNames()) { |
125 Set<String> mods = Stream.of(props.getProperty(key).split("\\s+")) |
128 Set<ModuleDescriptor> mods = |
126 .filter(mn -> finder.find(mn).isPresent()) |
129 Stream.of(props.getProperty(key).split("\\s+")) |
127 .map(String::trim) |
130 .map(String::trim) |
128 .collect(Collectors.toSet()); |
131 .flatMap(mn -> finder.find(mn).stream()) |
129 |
132 .map(ModuleReference::descriptor) |
130 // divide into 3 columns: Java SE, JDK, JavaFX |
133 .collect(toSet()); |
131 StringBuilder sb = new StringBuilder(); |
134 |
132 sb.append(mods.stream() |
|
133 .filter(mn -> mn.startsWith("java.")) |
|
134 .sorted() |
|
135 .map(GenDocsBundlePage::toHRef) |
|
136 .collect(Collectors.joining("\n"))); |
|
137 sb.append("</td>\n<td>") |
|
138 .append(mods.stream() |
|
139 .filter(mn -> mn.startsWith("jdk.")) |
|
140 .sorted() |
|
141 .map(GenDocsBundlePage::toHRef) |
|
142 .collect(Collectors.joining("\n"))); |
|
143 sb.append("</td>\n<td>"); |
|
144 if (mods.stream().anyMatch(mn -> mn.startsWith("javafx."))) { |
|
145 sb.append(mods.stream() |
|
146 .filter(mn -> mn.startsWith("javafx.")) |
|
147 .sorted() |
|
148 .map(GenDocsBundlePage::toHRef) |
|
149 .collect(Collectors.joining("\n"))); |
|
150 } |
|
151 String name = "@" + key.toUpperCase(Locale.ENGLISH) + "@"; |
135 String name = "@" + key.toUpperCase(Locale.ENGLISH) + "@"; |
152 groups.put(name, sb.toString()); |
136 moduleGroups.put(name, mods); |
153 } |
137 }; |
154 } |
138 } |
155 return groups; |
|
156 } |
|
157 |
|
158 static String toHRef(String mn) { |
|
159 return String.format("<a href=\"api/%s-summary.html\">%s</a><br>", mn, mn); |
|
160 } |
139 } |
161 |
140 |
162 void run(BufferedReader reader) throws IOException { |
141 void run(BufferedReader reader) throws IOException { |
163 if (Files.notExists(outputfile.getParent())) { |
142 if (Files.notExists(outputfile.getParent())) { |
164 Files.createDirectories(outputfile.getParent()); |
143 Files.createDirectories(outputfile.getParent()); |
172 |
151 |
173 String genOutputLine(String line) { |
152 String genOutputLine(String line) { |
174 if (line.contains(HEADER_TITLE)) { |
153 if (line.contains(HEADER_TITLE)) { |
175 line = line.replace(HEADER_TITLE, title); |
154 line = line.replace(HEADER_TITLE, title); |
176 } |
155 } |
177 if (line.contains("@")) { |
156 int i = line.indexOf('@'); |
178 for (Map.Entry<String,String> e: moduleGroups.entrySet()) { |
157 int j = line.indexOf('@', i+1); |
179 if (line.contains(e.getKey())) { |
158 if (i >= 0 && i < j) { |
180 line = line.replace(e.getKey(), e.getValue()); |
159 String name = line.substring(i, j+1); |
|
160 if (moduleGroups.containsKey(name)) { |
|
161 line = line.replace(name, formatModuleGroup(name)); |
|
162 } |
|
163 } |
|
164 return line; |
|
165 } |
|
166 |
|
167 String toHRef(ModuleDescriptor md) { |
|
168 String mn = md.name(); |
|
169 String formattedName; |
|
170 if (hasExportedAPIs(md)) { |
|
171 // has exported APIs |
|
172 formattedName = mn; |
|
173 } else if (!md.provides().isEmpty()) { |
|
174 // a provider |
|
175 formattedName = "<i>" + mn + "</i>"; |
|
176 } else { |
|
177 // a tool |
|
178 formattedName = "<i>" + mn + "</i>"; |
|
179 } |
|
180 return String.format("<a href=\"api/%s-summary.html\">%s</a>", |
|
181 mn, formattedName); |
|
182 } |
|
183 |
|
184 String formatModuleGroup(String groupName) { |
|
185 StringBuilder sb = new StringBuilder(); |
|
186 // organize in Java SE, JDK, JavaFX, JCP groups |
|
187 Set<ModuleDescriptor> modules = moduleGroups.get(groupName); |
|
188 Arrays.stream(ModuleGroup.values()) |
|
189 .forEach(g -> { |
|
190 Set<ModuleDescriptor> mods = modules.stream() |
|
191 .filter(md -> g.predicate.test(md.name())) |
|
192 .collect(toSet()); |
|
193 if (!mods.isEmpty()) { |
|
194 sb.append("<div class=" + g.cssClass + ">\n"); |
|
195 // modules with exported API |
|
196 mods.stream() |
|
197 .filter(this::hasExportedAPIs) |
|
198 .sorted(Comparator.comparing(ModuleDescriptor::name)) |
|
199 .map(this::toHRef) |
|
200 .forEach(m -> sb.append(m).append("\n")); |
|
201 |
|
202 // tools and providers |
|
203 mods.stream() |
|
204 .filter(md -> !hasExportedAPIs(md)) |
|
205 .sorted(Comparator.comparing(ModuleDescriptor::name)) |
|
206 .map(this::toHRef) |
|
207 .forEach(m -> sb.append(m).append("\n")); |
|
208 sb.append("</div>"); |
181 } |
209 } |
182 } |
210 }); |
183 } |
211 return sb.toString(); |
184 return line; |
212 } |
|
213 |
|
214 private boolean hasExportedAPIs(ModuleDescriptor md) { |
|
215 if (md.exports().stream().anyMatch(e -> !e.isQualified())) { |
|
216 return true; |
|
217 } |
|
218 // this should check if any indirect exports |
|
219 // checking requires transitive would be sufficient for JDK modules |
|
220 if (md.requires().stream() |
|
221 .map(ModuleDescriptor.Requires::modifiers) |
|
222 .anyMatch(mods -> mods.contains(ModuleDescriptor.Requires.Modifier.TRANSITIVE))) { |
|
223 return true; |
|
224 } |
|
225 return false; |
|
226 } |
|
227 |
|
228 private static final Set<String> NON_JAVA_SE_MODULES = |
|
229 Set.of("java.jnlp", "java.smartcardio"); |
|
230 |
|
231 /** |
|
232 * CSS class names are defined in docs-bundle-page.html |
|
233 */ |
|
234 enum ModuleGroup { |
|
235 JAVA_SE("javase", mn -> mn.startsWith("java.") && !NON_JAVA_SE_MODULES.contains(mn)), |
|
236 JDK("jdk", mn -> mn.startsWith("jdk.")), |
|
237 JAVAFX("javafx", mn -> mn.startsWith("javafx.")), |
|
238 NON_JAVA_SE("jcp", NON_JAVA_SE_MODULES::contains); |
|
239 |
|
240 final String cssClass; |
|
241 final Predicate<String> predicate; |
|
242 ModuleGroup(String cssClass, Predicate<String> predicate) { |
|
243 this.cssClass = cssClass; |
|
244 this.predicate = predicate; |
|
245 } |
185 } |
246 } |
186 } |
247 } |