| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.html.editor.lib; |
| |
| import java.lang.ref.Reference; |
| import java.lang.ref.SoftReference; |
| import java.util.ArrayList; |
| import java.util.Iterator; |
| import java.util.List; |
| import org.netbeans.api.html.lexer.HTMLTokenId; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.html.editor.lib.api.elements.Element; |
| |
| /** |
| * Creates instances of Iterator<Element> which used weakly cached elements. |
| * |
| * The parsed elements are cached in blocks of 100 elements, each block is weakly referenced. |
| * |
| * @author mfukala@netbeans.org |
| */ |
| public class ElementsParserCache { |
| |
| /* not private final for unit testing */ static int CACHE_BLOCK_SIZE = 1000; //number of Element-s in one cache block |
| /* test */ final List<CacheBlock> cacheBlocks = new ArrayList<>(); |
| |
| private final CharSequence sourceCode; |
| private final TokenSequence<HTMLTokenId> tokenSequence; |
| |
| public ElementsParserCache(CharSequence sourceCode, TokenSequence<HTMLTokenId> tokenSequence) { |
| this.sourceCode = sourceCode; |
| this.tokenSequence = tokenSequence; |
| } |
| |
| public Iterator<Element> createElementsIterator() { |
| return new Iterator<Element>() { |
| |
| private int index = 0; |
| |
| @Override |
| public boolean hasNext() { |
| return getCacheBlock().getEndIndex() > index; |
| } |
| |
| @Override |
| public Element next() { |
| return getCacheBlock().getElementAtIndex(index++); |
| } |
| |
| //notice: new empty block will be created at the end of the source |
| // if the number of elements modulo CACHE_BLOCK_SIZE == 0; |
| private CacheBlock getCacheBlock() { |
| |
| synchronized(cacheBlocks) { |
| int blockIndex = index / CACHE_BLOCK_SIZE; |
| |
| CacheBlock item = cacheBlocks.size() > blockIndex ? cacheBlocks.get(blockIndex) : null; |
| if (item == null) { |
| //no data, load |
| |
| //first token of the new block - either 0 or last block end token index + 1 |
| int firstTokenIndex = blockIndex == 0 ? 0 : cacheBlocks.get(blockIndex - 1).getLastTokenIndex() + 1; |
| |
| item = new CacheBlock(sourceCode, tokenSequence, index, firstTokenIndex); |
| |
| assert blockIndex == cacheBlocks.size(); //always last |
| |
| cacheBlocks.add(blockIndex, item); |
| } |
| return item; |
| } |
| } |
| |
| @Override |
| public void remove() { |
| //no-op |
| } |
| }; |
| |
| } |
| |
| static class CacheBlock { |
| |
| /* test */ int blockReads = 0; |
| |
| Reference<CacheBlockContent> blockReference; |
| private final int startIndex; |
| private final int endIndex; |
| private final int startOffset; |
| private final int endOffset; |
| private final int firstTokenIndex; |
| private final int lastTokenIndex; |
| private final CharSequence code; |
| private final TokenSequence<HTMLTokenId> tokenSequence; |
| |
| private CacheBlock(CharSequence code, TokenSequence<HTMLTokenId> tokenSequence, int firstElementIndex, int firstTokenIndex) { |
| this.code = code; |
| this.tokenSequence = tokenSequence; |
| |
| this.startIndex = firstElementIndex; |
| this.firstTokenIndex = firstTokenIndex; |
| |
| CacheBlockContent block = new CacheBlockContent(code, tokenSequence, firstTokenIndex); |
| int blockSize = block.getElements().size(); |
| this.endIndex = firstElementIndex + blockSize; |
| |
| this.startOffset = blockSize == 0 ? -1 : block.getFirstElement().from(); |
| |
| this.endOffset = blockSize == 0 ? -1 : block.getLastElement().to(); |
| this.lastTokenIndex = block.getLastTokenIndex(); |
| |
| blockReads++; |
| |
| blockReference = new SoftReference<>(block); |
| } |
| |
| public int getStartIndex() { |
| return startIndex; |
| } |
| |
| public int getEndIndex() { |
| return endIndex; |
| } |
| |
| public int getStartOffset() { |
| return startOffset; |
| } |
| |
| public int getEndOffset() { |
| return endOffset; |
| } |
| |
| public int getFirstTokenIndex() { |
| return firstTokenIndex; |
| } |
| |
| public int getLastTokenIndex() { |
| return lastTokenIndex; |
| } |
| |
| public Element getElementAtIndex(int index) { |
| return getElements().get(index - getStartIndex()); |
| } |
| |
| //synchronized as the new CacheBlockContent(...) creates a new instance |
| //of ElementsParser which however operates on shared TokenSequence and |
| //repositions it. |
| public synchronized List<Element> getElements() { |
| CacheBlockContent block = blockReference.get(); |
| if (block == null) { |
| //reload the content |
| block = new CacheBlockContent(code, tokenSequence, startIndex); |
| |
| blockReads++; |
| |
| blockReference = new SoftReference<>(block); |
| |
| // System.out.println("block at " + getStartIndex() + " - cache reloaded " + blockReads + " times"); |
| } |
| return block.getElements(); |
| } |
| |
| @Override |
| public String toString() { |
| return new StringBuilder() |
| .append("CacheBlock(hash=") |
| .append(hashCode()) |
| .append(",items=") |
| .append(getElements().size()) |
| .toString(); //NOI18N |
| } |
| |
| } |
| |
| |
| private static class CacheBlockContent { |
| |
| private final List<Element> elements; |
| private final int firstTokenIndex; |
| private final int lastTokenIndex; |
| |
| private CacheBlockContent(CharSequence code, TokenSequence<HTMLTokenId> tokenSequence, int firstTokenIndex) { |
| this.firstTokenIndex = firstTokenIndex; |
| //load the elements |
| ElementsParser parser = ElementsParser.forTokenIndex(code, tokenSequence, firstTokenIndex); |
| elements = new ArrayList<>(CACHE_BLOCK_SIZE); |
| int limit = CACHE_BLOCK_SIZE; |
| while (limit-- > 0 && parser.hasNext()) { |
| elements.add(parser.next()); |
| } |
| lastTokenIndex = tokenSequence.index(); |
| |
| } |
| |
| List<Element> getElements() { |
| return elements; |
| } |
| |
| Element getFirstElement() { |
| return elements.get(0); |
| } |
| |
| Element getLastElement() { |
| return elements.get(elements.size() - 1); |
| } |
| |
| int getFirstTokenIndex() { |
| return firstTokenIndex; |
| } |
| |
| int getLastTokenIndex() { |
| return lastTokenIndex; |
| } |
| |
| } |
| } |