1. package lexers_test
    
  2. 
    
  3. import (
    
  4. 	"testing"
    
  5. 
    
  6. 	assert "github.com/alecthomas/assert/v2"
    
  7. 
    
  8. 	"github.com/alecthomas/chroma/v2"
    
  9. 	"github.com/alecthomas/chroma/v2/lexers"
    
  10. )
    
  11. 
    
  12. const lexerBenchSource = `/*
    
  13.  * Licensed to the Apache Software Foundation (ASF) under one or more
    
  14.  * contributor license agreements. See the NOTICE file distributed with
    
  15.  * this work for additional information regarding copyright ownership.
    
  16.  * The ASF licenses this file to You under the Apache License, Version 2.0
    
  17.  * (the "License"); you may not use this file except in compliance with
    
  18.  * the License. You may obtain a copy of the License at
    
  19.  *
    
  20.  *    http://www.apache.org/licenses/LICENSE-2.0
    
  21.  *
    
  22.  * Unless required by applicable law or agreed to in writing, software
    
  23.  * distributed under the License is distributed on an "AS IS" BASIS,
    
  24.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    
  25.  * See the License for the specific language governing permissions and
    
  26.  * limitations under the License.
    
  27.  */
    
  28. package org.apache.kafka.server.common;
    
  29. 
    
  30. import org.apache.kafka.common.utils.Utils;
    
  31. 
    
  32. import java.io.BufferedReader;
    
  33. import java.io.BufferedWriter;
    
  34. import java.io.File;
    
  35. import java.io.FileOutputStream;
    
  36. import java.io.IOException;
    
  37. import java.io.OutputStreamWriter;
    
  38. import java.nio.charset.StandardCharsets;
    
  39. import java.nio.file.FileAlreadyExistsException;
    
  40. import java.nio.file.Files;
    
  41. import java.nio.file.Path;
    
  42. import java.nio.file.Paths;
    
  43. import java.util.ArrayList;
    
  44. import java.util.Collection;
    
  45. import java.util.Collections;
    
  46. import java.util.List;
    
  47. import java.util.Optional;
    
  48. 
    
  49. /**
    
  50.  * This class represents a utility to capture a checkpoint in a file. It writes down to the file in the below format.
    
  51.  *
    
  52.  * ========= File beginning =========
    
  53.  * version: int
    
  54.  * entries-count: int
    
  55.  * entry-as-string-on-each-line
    
  56.  * ========= File end ===============
    
  57.  *
    
  58.  * Each entry is represented as a string on each line in the checkpoint file. {@link EntryFormatter} is used
    
  59.  * to convert the entry into a string and vice versa.
    
  60.  *
    
  61.  * @param <T> entry type.
    
  62.  */
    
  63. public class CheckpointFile<T> {
    
  64. 
    
  65.     private final int version;
    
  66.     private final EntryFormatter<T> formatter;
    
  67.     private final Object lock = new Object();
    
  68.     private final Path absolutePath;
    
  69.     private final Path tempPath;
    
  70. 
    
  71.     public CheckpointFile(File file,
    
  72.                           int version,
    
  73.                           EntryFormatter<T> formatter) throws IOException {
    
  74.         this.version = version;
    
  75.         this.formatter = formatter;
    
  76.         try {
    
  77.             // Create the file if it does not exist.
    
  78.             Files.createFile(file.toPath());
    
  79.         } catch (FileAlreadyExistsException ex) {
    
  80.             // Ignore if file already exists.
    
  81.         }
    
  82.         absolutePath = file.toPath().toAbsolutePath();
    
  83.         tempPath = Paths.get(absolutePath.toString() + ".tmp");
    
  84.     }
    
  85. 
    
  86.     public void write(Collection<T> entries) throws IOException {
    
  87.         synchronized (lock) {
    
  88.             // write to temp file and then swap with the existing file
    
  89.             try (FileOutputStream fileOutputStream = new FileOutputStream(tempPath.toFile());
    
  90.                  BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8))) {
    
  91.                 // Write the version
    
  92.                 writer.write(Integer.toString(version));
    
  93.                 writer.newLine();
    
  94. 
    
  95.                 // Write the entries count
    
  96.                 writer.write(Integer.toString(entries.size()));
    
  97.                 writer.newLine();
    
  98. 
    
  99.                 // Write each entry on a new line.
    
  100.                 for (T entry : entries) {
    
  101.                     writer.write(formatter.toString(entry));
    
  102.                     writer.newLine();
    
  103.                 }
    
  104. 
    
  105.                 writer.flush();
    
  106.                 fileOutputStream.getFD().sync();
    
  107.             }
    
  108. 
    
  109.             Utils.atomicMoveWithFallback(tempPath, absolutePath);
    
  110.         }
    
  111.     }
    
  112. 
    
  113.     public List<T> read() throws IOException {
    
  114.         synchronized (lock) {
    
  115.             try (BufferedReader reader = Files.newBufferedReader(absolutePath)) {
    
  116.                 CheckpointReadBuffer<T> checkpointBuffer = new CheckpointReadBuffer<>(absolutePath.toString(), reader, version, formatter);
    
  117.                 return checkpointBuffer.read();
    
  118.             }
    
  119.         }
    
  120.     }
    
  121. 
    
  122.     private static class CheckpointReadBuffer<T> {
    
  123. 
    
  124.         private final String location;
    
  125.         private final BufferedReader reader;
    
  126.         private final int version;
    
  127.         private final EntryFormatter<T> formatter;
    
  128. 
    
  129.         CheckpointReadBuffer(String location,
    
  130.                              BufferedReader reader,
    
  131.                              int version,
    
  132.                              EntryFormatter<T> formatter) {
    
  133.             this.location = location;
    
  134.             this.reader = reader;
    
  135.             this.version = version;
    
  136.             this.formatter = formatter;
    
  137.         }
    
  138. 
    
  139.         List<T> read() throws IOException {
    
  140.             String line = reader.readLine();
    
  141.             if (line == null)
    
  142.                 return Collections.emptyList();
    
  143. 
    
  144.             int readVersion = toInt(line);
    
  145.             if (readVersion != version) {
    
  146.                 throw new IOException("Unrecognised version:" + readVersion + ", expected version: " + version
    
  147.                                               + " in checkpoint file at: " + location);
    
  148.             }
    
  149. 
    
  150.             line = reader.readLine();
    
  151.             if (line == null) {
    
  152.                 return Collections.emptyList();
    
  153.             }
    
  154.             int expectedSize = toInt(line);
    
  155.             List<T> entries = new ArrayList<>(expectedSize);
    
  156.             line = reader.readLine();
    
  157.             while (line != null) {
    
  158.                 Optional<T> maybeEntry = formatter.fromString(line);
    
  159.                 if (!maybeEntry.isPresent()) {
    
  160.                     throw buildMalformedLineException(line);
    
  161.                 }
    
  162.                 entries.add(maybeEntry.get());
    
  163.                 line = reader.readLine();
    
  164.             }
    
  165. 
    
  166.             if (entries.size() != expectedSize) {
    
  167.                 throw new IOException("Expected [" + expectedSize + "] entries in checkpoint file ["
    
  168.                                               + location + "], but found only [" + entries.size() + "]");
    
  169.             }
    
  170. 
    
  171.             return entries;
    
  172.         }
    
  173. 
    
  174.         private int toInt(String line) throws IOException {
    
  175.             try {
    
  176.                 return Integer.parseInt(line);
    
  177.             } catch (NumberFormatException e) {
    
  178.                 throw buildMalformedLineException(line);
    
  179.             }
    
  180.         }
    
  181. 
    
  182.         private IOException buildMalformedLineException(String line) {
    
  183.             return new IOException(String.format("Malformed line in checkpoint file [%s]: %s", location, line));
    
  184.         }
    
  185.     }
    
  186. 
    
  187.     /**
    
  188.      * This is used to convert the given entry of type {@code T} into a string and vice versa.
    
  189.      *
    
  190.      * @param <T> entry type
    
  191.      */
    
  192.     public interface EntryFormatter<T> {
    
  193. 
    
  194.         /**
    
  195.          * @param entry entry to be converted into string.
    
  196.          * @return String representation of the given entry.
    
  197.          */
    
  198.         String toString(T entry);
    
  199. 
    
  200.         /**
    
  201.          * @param value string representation of an entry.
    
  202.          * @return entry converted from the given string representation if possible. {@link Optional#empty()} represents
    
  203.          * that the given string representation could not be converted into an entry.
    
  204.          */
    
  205.         Optional<T> fromString(String value);
    
  206.     }
    
  207. }
    
  208. `
    
  209. 
    
  210. func Benchmark(b *testing.B) {
    
  211. 	b.ReportAllocs()
    
  212. 	for i := 0; i < b.N; i++ {
    
  213. 		it, err := lexers.GlobalLexerRegistry.Get("Java").Tokenise(nil, lexerBenchSource)
    
  214. 		assert.NoError(b, err)
    
  215. 		for t := it(); t != chroma.EOF; t = it() {
    
  216. 		}
    
  217. 	}
    
  218. }