1111
1212import org .elasticsearch .Build ;
1313import org .elasticsearch .core .SuppressForbidden ;
14- import org .elasticsearch .xcontent .XContentFactory ;
15- import org .elasticsearch .xcontent .XContentParser ;
16- import org .elasticsearch .xcontent .XContentParserConfiguration ;
17- import org .elasticsearch .xcontent .XContentType ;
1814
15+ import java .io .BufferedReader ;
1916import java .io .FileNotFoundException ;
2017import java .io .IOException ;
2118import java .io .InputStream ;
19+ import java .io .InputStreamReader ;
2220import java .net .URL ;
23- import java .util . LinkedHashMap ;
21+ import java .nio . charset . StandardCharsets ;
2422import java .util .Map ;
2523import java .util .regex .Pattern ;
2624
2725/**
2826 * Encapsulates links to pages in the reference docs, so that for example we can include URLs in logs and API outputs. Each instance's
2927 * {@link #toString()} yields (a string representation of) a URL for the relevant docs. Links are defined in the resource file
30- * {@code reference-docs-links.json } which must include definitions for exactly the set of values of this enum.
28+ * {@code reference-docs-links.txt } which must include definitions for exactly the set of values of this enum.
3129 */
3230public enum ReferenceDocs {
3331 /*
34- * Note that the docs subsystem parses {@code reference-docs-links.json} with regexes, not a JSON parser, so the whitespace in the file
35- * is important too. See {@code sub check_elasticsearch_links} in {@code https://github.com/elastic/docs/blob/master/build_docs.pl} for
36- * more details.
32+ * Note that the docs subsystem parses {@code reference-docs-links.txt} differently. See {@code sub check_elasticsearch_links} in
33+ * {@code https://github.com/elastic/docs/blob/master/build_docs.pl} for more details.
3734 *
3835 * Also note that the docs are built from the HEAD of each minor release branch, so in principle docs can move around independently of
3936 * the ES release process. To avoid breaking any links that have been baked into earlier patch releases, you may only add links in a
@@ -89,7 +86,7 @@ public enum ReferenceDocs {
8986 private static final Map <String , String > linksBySymbol ;
9087
9188 static {
92- try (var resourceStream = readFromJarResourceUrl (ReferenceDocs .class .getResource ("reference-docs-links.json " ))) {
89+ try (var resourceStream = readFromJarResourceUrl (ReferenceDocs .class .getResource ("reference-docs-links.txt " ))) {
9390 linksBySymbol = Map .copyOf (readLinksBySymbol (resourceStream ));
9491 } catch (Exception e ) {
9592 assert false : e ;
@@ -101,34 +98,69 @@ public enum ReferenceDocs {
10198 static final String CURRENT_VERSION_COMPONENT = "current" ;
10299 static final String VERSION_COMPONENT = getVersionComponent (Build .current ().version (), Build .current ().isSnapshot ());
103100
104- static Map <String , String > readLinksBySymbol (InputStream inputStream ) throws Exception {
105- try (var parser = XContentFactory .xContent (XContentType .JSON ).createParser (XContentParserConfiguration .EMPTY , inputStream )) {
106- final var result = parser .map (LinkedHashMap ::new , XContentParser ::text );
107- final var iterator = result .keySet ().iterator ();
108- for (int i = 0 ; i < values ().length ; i ++) {
109- final var expected = values ()[i ].name ();
110- if (iterator .hasNext () == false ) {
111- throw new IllegalStateException ("ran out of values at index " + i + ": expecting " + expected );
112- }
113- final var actual = iterator .next ();
114- if (actual .equals (expected ) == false ) {
115- throw new IllegalStateException ("mismatch at index " + i + ": found " + actual + " but expected " + expected );
116- }
101+ static final int SYMBOL_COLUMN_WIDTH = 64 ; // increase as needed to accommodate yet longer symbols
102+
103+ static Map <String , String > readLinksBySymbol (InputStream inputStream ) throws IOException {
104+ final var padding = " " .repeat (SYMBOL_COLUMN_WIDTH );
105+
106+ record LinksBySymbolEntry (String symbol , String link ) implements Map .Entry <String , String > {
107+ @ Override
108+ public String getKey () {
109+ return symbol ;
117110 }
118- if (iterator .hasNext ()) {
119- throw new IllegalStateException ("found unexpected extra value: " + iterator .next ());
111+
112+ @ Override
113+ public String getValue () {
114+ return link ;
115+ }
116+
117+ @ Override
118+ public String setValue (String value ) {
119+ assert false ;
120+ throw new UnsupportedOperationException ();
120121 }
122+ }
121123
122- // We must only link to anchors with fixed IDs (defined by [[fragment-name]] in the docs) because auto-generated fragment IDs
123- // depend on the heading text and are too easy to break inadvertently. Auto-generated fragment IDs begin with an underscore.
124- for (final var entry : result .entrySet ()) {
125- if (entry .getValue ().startsWith ("_" ) || entry .getValue ().contains ("#_" )) {
126- throw new IllegalStateException ("found auto-generated fragment ID at " + entry .getKey ());
124+ final var symbolCount = values ().length ;
125+ final var linksBySymbolEntries = new LinksBySymbolEntry [symbolCount ];
126+
127+ try (var reader = new BufferedReader (new InputStreamReader (inputStream , StandardCharsets .UTF_8 ))) {
128+ for (int i = 0 ; i < symbolCount ; i ++) {
129+ final var currentLine = reader .readLine ();
130+ final var symbol = values ()[i ].name ();
131+ if (currentLine == null ) {
132+ throw new IllegalStateException ("links resource truncated at line " + (i + 1 ));
133+ }
134+ if (currentLine .startsWith (symbol + " " ) == false ) {
135+ throw new IllegalStateException (
136+ "unexpected symbol at line " + (i + 1 ) + ": expected line starting with [" + symbol + " ]"
137+ );
138+ }
139+ final var link = currentLine .substring (SYMBOL_COLUMN_WIDTH ).trim ();
140+ if (Strings .hasText (link ) == false ) {
141+ throw new IllegalStateException ("no link found for [" + symbol + "] at line " + (i + 1 ));
142+ }
143+ final var expectedLine = (symbol + padding ).substring (0 , SYMBOL_COLUMN_WIDTH ) + link ;
144+ if (currentLine .equals (expectedLine ) == false ) {
145+ throw new IllegalStateException ("unexpected content at line " + (i + 1 ) + ": expected [" + expectedLine + "]" );
127146 }
147+
148+ // We must only link to anchors with fixed IDs (defined by [[fragment-name]] in the docs) because auto-generated fragment
149+ // IDs depend on the heading text and are too easy to break inadvertently. Auto-generated fragment IDs begin with "_"
150+ if (link .startsWith ("_" ) || link .contains ("#_" )) {
151+ throw new IllegalStateException (
152+ "found auto-generated fragment ID in link [" + link + "] for [" + symbol + "] at line " + (i + 1 )
153+ );
154+ }
155+ linksBySymbolEntries [i ] = new LinksBySymbolEntry (symbol , link );
128156 }
129157
130- return result ;
158+ if (reader .readLine () != null ) {
159+ throw new IllegalStateException ("unexpected trailing content at line " + (symbolCount + 1 ));
160+ }
131161 }
162+
163+ return Map .ofEntries (linksBySymbolEntries );
132164 }
133165
134166 /**
0 commit comments