class Annotation {

    private static final Logger LOGGER = LoggerFactory.getLogger(Annotation.class);

    AnnotationData annotationData;

    private final Map<String, String> desc = new HashMap<>(); // maps revision to description
    private final Map<String, Integer> fileVersions = new HashMap<>(); // maps revision to file version

    private final LazilyInstantiate<Map<String, String>> colors = LazilyInstantiate.using(this::generateColors);

    public Annotation(String filename) {
        annotationData = new AnnotationData(filename);
    }

    public Annotation(AnnotationData annotationData) {
        this.annotationData = annotationData;
    }

    void addLine(String revision, String author, boolean enabled) {
        annotationData.addLine(revision, author, enabled, null);
    }

    void addLine(String revision, String author, boolean enabled, String displayRevision) {
      annotationData.addLine(revision, author, enabled, displayRevision);
    }

    public String getFilename() {
        return annotationData.getFilename();
    }

    public void setRevision(String revision) {
        annotationData.setRevision(revision);
    }

    /**
     * @return revision this annotation was generated for
     */
    public String getRevision() {
        return annotationData.getRevision();
    }

    /**
     * Returns the size of the file (number of lines).
     *
     * @return number of lines
     */
    public int size() {
        return annotationData.size();
    }

    /**
     * Gets the revision for the last change to the specified line.
     *
     * @param line line number (counting from 1)
     * @return revision string, or an empty string if there is no information
     * about the specified line
     */
    public String getRevision(int line) {
        return annotationData.getRevision(line);
    }

    /**
     * Gets the revision for the last change to the specified line in a format that may be specifically for display purposes.
     *
     * @param line line number (counting from 1)
     * @return revision string, or an empty string if there is no information
     * about the specified line
     */
    public String getRevisionForDisplay(int line) {
      return annotationData.getRevisionForDisplay(line);
    }

    /**
     * Gets all revisions that are in use, first is the lowest one (sorted using natural order).
     *
     * @return list of all revisions the file has
     */
    public Set<String> getRevisions() {
        return annotationData.getRevisions();
    }

    @TestOnly
    Set<String> getAuthors() {
        return annotationData.getAuthors();
    }

    /**
     * Gets the author who last modified the specified line.
     *
     * @param line line number (counting from 1)
     * @return author, or an empty string if there is no information about the
     * specified line
     */
    public String getAuthor(int line) {
        return annotationData.getAuthor(line);
    }

    public int getWidestRevision() {
        return annotationData.getWidestRevision();
    }

    public int getWidestAuthor() {
        return annotationData.getWidestAuthor();
    }

    /**
     * Gets the enabled state for the last change to the specified line.
     *
     * @param line line number (counting from 1)
     * @return true if the xref for this revision is enabled, false otherwise
     */
    public boolean isEnabled(int line) {
        return annotationData.isEnabled(line);
    }

    void addDesc(String revision, String description) {
        desc.put(revision, description);
    }

    public String getDesc(String revision) {
        return desc.get(revision);
    }

    void addFileVersion(String revision, int fileVersion) {
        fileVersions.put(revision, fileVersion);
    }

    /**
     * Translates repository revision number into file version.
     * @param revision revision number
     * @return file version number. 0 if unknown. 1 first version of file, etc.
     */
    public int getFileVersion(String revision) {
        return fileVersions.getOrDefault(revision, 0);
    }

    /**
     * @return Count of revisions on this file.
     */
    public int getFileVersionsCount() {
        return fileVersions.size();
    }

    /**
     * Return the color palette for the annotated file.
     *
     * @return map of (revision, css color string) for each revision in {@code getRevisions()}
     * @see #generateColors()
     */
    public Map<String, String> getColors() {
        return colors.get();
    }

    /**
     * Generate the color palette for the annotated revisions.
     * <p>
     * First, take into account revisions which are tracked in history fields
     * and compute their color. Secondly, use all other revisions in order
     * which is undefined and generate the rest of the colors for them.
     *
     * @return map of (revision, css color string) for each revision in {@code getRevisions()}
     * @see #getRevisions()
     */
    private Map<String, String> generateColors() {<FILL_FUNCTION_BODY>}

    //TODO below might be useless, need to test with more SCMs and different commit messages
    // to see if it will not be useful, if title attribute of <a> loses it's breath
    public void writeTooltipMap(Writer out) throws IOException {
        out.append("<script type=\"text/javascript\">\nvar desc = new Object();\n");
        for (Entry<String, String> entry : desc.entrySet()) {
            out.append("desc['");
            out.append(entry.getKey());
            out.append("'] = \"");
            out.append(entry.getValue());
            out.append("\";\n");
        }
        out.append("</script>\n");
    }

    @Override
    public String toString() {
        StringWriter sw = new StringWriter();
        for (AnnotationLine annotationLine : annotationData.getLines()) {
            sw.append(annotationLine.getRevision());
            sw.append("|");
            sw.append(annotationLine.getAuthor());
            sw.append(": \n");
        }

        try {
            writeTooltipMap(sw);
        } catch (IOException e) {
            LOGGER.finest(e.getMessage());
        }

        return sw.toString();
    }
}

        List<Color> orderedColors = RainbowColorGenerator.getOrderedColors();

        Map<String, String> colorMap = new HashMap<>();
        final List<String> revisions =
                getRevisions()
                        .stream()
                        /*
                         * Greater file version means more recent revision.
                         * 0 file version means unknown revision (untracked by history entries).
                         *
                         * The result of this sort is:
                         * 1) known revisions sorted from most recent to least recent
                         * 2) all other revisions in non-determined order
                         */
                        .sorted(Comparator.comparingInt(this::getFileVersion).reversed())
                        .collect(Collectors.toList());

        final int nColors = orderedColors.size();
        final double colorsPerBucket = (double) nColors / getRevisions().size();

        revisions.forEach(revision -> {
            final int lineVersion = getRevisions().size() - getFileVersion(revision);
            final double bucketTotal = colorsPerBucket * lineVersion;
            final int bucketIndex = (int) Math.max(
                    Math.min(Math.floor(bucketTotal), nColors - 1.0), 0);
            Color color = orderedColors.get(bucketIndex);
            colorMap.put(revision, String.format("rgb(%d, %d, %d)", color.red, color.green, color.blue));
        });

        return colorMap;
    
<no_super_class>