org.netbeans.modules.parsing.api.Snapshot Maven / Gradle / Ivy
/*
* 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.parsing.api;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.Document;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.editor.mimelookup.MimePath;
import org.netbeans.api.lexer.InputAttributes;
import org.netbeans.api.lexer.Language;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.modules.parsing.impl.Utilities;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.util.Parameters;
/**
* Snapshot represents some part of text. Snapshot can be created from
* {@link Source} representing file or document, or from some other Snapshot.
* So Snapshot can represent some block of code written
* in different language embedded inside some top level language too. It can contain
* some generated parts of code that is not contained in the original
* file. Snapshot is immutable. It means that Snapshot created
* from document opened in editor contains some copy of original text.
* You do not need to call Snapshot methods under
* any locks, but on other hand Snapshot may not be up to date.
*
* @author Jan Jancura
*/
public final class Snapshot {
private static final Logger LOG = Logger.getLogger(Snapshot.class.getName());
private final CharSequence text;
/* package */ final int[] lineStartOffsets;
private final MimePath mimePath;
/* package */ final int[][] currentToOriginal;
private final int[][] originalToCurrent;
private final Source source;
private TokenHierarchy> tokenHierarchy;
private Snapshot (
CharSequence text,
int [] lineStartOffsets,
Source source,
MimePath mimePath,
int[][] currentToOriginal,
int[][] originalToCurrent
) {
this.text = text;
this.lineStartOffsets = lineStartOffsets;
this.source = source;
this.mimePath = mimePath;
this.currentToOriginal =
currentToOriginal;
this.originalToCurrent =
originalToCurrent;
}
@NonNull
static Snapshot create(
CharSequence text,
int [] lineStartOffsets,
Source source,
MimePath mimePath,
int[][] currentToOriginal,
int[][] originalToCurrent) {
final int textLength = text.length();
if (textLength > Utilities.getMaxFileSize()) {
text = ""; //NOI18N
LOG.log(
Level.WARNING,
"Embedding in file {0} of type: {1} of size: {2} has been ignored due to large size. Embeddings large then {3} chars are ignored, you can increase the size by parse.max.file.size property.", //NOI18N
new Object[] {
source.getFileObject() == null ?
"" : //NOI18N
FileUtil.getFileDisplayName(source.getFileObject()),
mimePath,
textLength,
Utilities.getMaxFileSize()
});
}
return new Snapshot(
text,
lineStartOffsets,
source,
mimePath,
currentToOriginal,
originalToCurrent);
}
/**
* Creates a new embedding form part of this snapshot defined by offset and length.
*
* @param offset A start offset of the new embedding. Start offset
* is relative to the current snapshot.
* @param length A length of the new embedding.
* @param mimeType Mime type of the new embedding.
* @return The new embedding.
* @throws IndexOutOfBoundsException when bounds of the new embedding exceeds
* original snapshot.
*/
public Embedding create (
int offset,
int length,
String mimeType
) {
if (offset < 0 || length < 0)
throw new ArrayIndexOutOfBoundsException ("offset=" + offset + ", length=" + length); //NOI18N
if (offset + length > getText ().length ())
throw new ArrayIndexOutOfBoundsException ("offset=" + offset + ", length=" + length + ", snapshot-length=" + getText().length()); //NOI18N
List newCurrentToOriginal = new ArrayList ();
List newOriginalToCurrent = new ArrayList ();
int i = 1;
while (i < currentToOriginal.length && currentToOriginal [i] [0] <= offset) i++;
if (currentToOriginal [i - 1] [1] < 0)
newCurrentToOriginal.add (new int[] {
0, currentToOriginal [i - 1] [1]
});
else {
newCurrentToOriginal.add (new int[] {
0, currentToOriginal [i - 1] [1] + offset - currentToOriginal [i - 1] [0]
});
newOriginalToCurrent.add (new int[] {
currentToOriginal [i - 1] [1] + offset - currentToOriginal [i - 1] [0], 0
});
}
for (; i < currentToOriginal.length && currentToOriginal [i] [0] < offset + length; i++) {
newCurrentToOriginal.add (new int[] {
currentToOriginal [i] [0] - offset, currentToOriginal [i] [1]
});
if (currentToOriginal [i] [1] >= 0)
newOriginalToCurrent.add (new int[] {
currentToOriginal [i] [1], currentToOriginal [i] [0] - offset
});
else
newOriginalToCurrent.add (new int[] {
currentToOriginal [i - 1] [1] + currentToOriginal [i] [0] - currentToOriginal [i - 1] [0], -1
});
}
if (newOriginalToCurrent.size () > 0 &&
newOriginalToCurrent.get (newOriginalToCurrent.size () - 1) [1] >= 0
)
newOriginalToCurrent.add (new int[] {
newOriginalToCurrent.get (newOriginalToCurrent.size () - 1) [0] +
length -
newOriginalToCurrent.get (newOriginalToCurrent.size () - 1) [1],
-1
});
MimePath newMimePath = MimePath.get (mimePath, mimeType);
Snapshot snapshot = create (
new CharSequenceView(getText(), offset, offset + length),
null,
source,
newMimePath,
newCurrentToOriginal.toArray (new int [0][]),
newOriginalToCurrent.toArray (new int [0][])
);
return new Embedding (
snapshot,
newMimePath
);
}
/**
* Creates a new embedding for given charSequence.
*
* @param charSequence A text of new embedding.
* @param mimeType Mime type of the new embedding.
* @return The new embedding.
*/
public Embedding create (
CharSequence charSequence,
String mimeType
) {
MimePath newMimePath = MimePath.get (mimePath, mimeType);
return new Embedding (
create (
charSequence,
null,
source,
newMimePath,
new int[][] {new int[] {0, -1}}, new int[][] {}
),
newMimePath
);
}
/**
* Returns content of this snapshot.
*
* @return text of this snapshot
*/
public CharSequence getText (
) {
return text;
}
/**
* Returns this snapshot's mime type.
*
* @return this snapshot mime type.
*/
public String getMimeType (
) {
return mimePath.getMimeType (mimePath.size () - 1);
}
/**
* Returns this snapshot's mime path.
*
* @return this snapshot mime type.
*/
public MimePath getMimePath (
) {
return mimePath;
}
/**
* Get the TokenHierarchy
lexed from this snapshot.
*
* @return TokenHierarchy
created by a Lexer
registered
* for this Snapshot
's mime type or null
if there is
* no such Lexer
.
* @since 1.1
*/
public TokenHierarchy> getTokenHierarchy() {
if (tokenHierarchy == null) {
Language extends TokenId> lang = Language.find(getMimeType());
if (lang != null) {
//copy the InputAttributes from the source document to the
//charsequence token hierarchy
Document sourceDocument = source.getDocument(false);
InputAttributes inputAttrs = sourceDocument != null ? (InputAttributes)sourceDocument.getProperty(InputAttributes.class) : null;
tokenHierarchy = TokenHierarchy.create(text, false, lang, null, inputAttrs);
}
}
return tokenHierarchy;
}
/**
* Gets an offset in the original source corresponding to an offset in this snapshot.
* The method will return -1
if snapshotOffset
can't
* be translated back to the original source. For example on the snapshotOffset
* is in a "virtual" area of text. That is in a text generated by some EmbeddingProvider
,
* which has no representation in the top level source code.
*
* @param snapshotOffset The offset in this snapshot.
*
* @return The offset in the original source or -1
.
*/
public int getOriginalOffset (
int snapshotOffset
) {
if (snapshotOffset < 0)
return -1;
if (snapshotOffset > getText ().length ())
return -1;
int low = 0;
int high = currentToOriginal.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int cmp = currentToOriginal [mid] [0];
if (cmp > snapshotOffset)
high = mid - 1;
else
if (mid == currentToOriginal.length - 1 ||
currentToOriginal [mid + 1] [0] > snapshotOffset
) {
if (currentToOriginal [mid] [1] < 0) {
if (snapshotOffset == cmp && mid > 0)
return snapshotOffset - currentToOriginal [mid - 1] [0] + currentToOriginal [mid - 1] [1];
else
return currentToOriginal [mid] [1];
} else
return snapshotOffset - currentToOriginal [mid] [0] + currentToOriginal [mid] [1];
} else
low = mid + 1;
} // while
return -1;
}
/**
* Gets an offset in this snapshot corresponding to an offset
* in the original source. The method can return -1
if originalOffset
* points to an area in the original source, which is not part of this snapshot.
*
* @param originalOffset The offset in the original source.
*
* @return The offset in this snapshot or -1
.
*/
public int getEmbeddedOffset (
int originalOffset
) {
int low = 0;
int high = originalToCurrent.length - 1;
while (low <= high) {
int mid = (low + high) >> 1;
int cmp = originalToCurrent [mid] [0];
if (cmp > originalOffset)
high = mid - 1;
else
if (mid == originalToCurrent.length - 1 ||
originalToCurrent [mid + 1] [0] > originalOffset
)
if (originalToCurrent [mid] [1] < 0)
if (originalOffset == cmp && mid > 0)
return originalOffset - originalToCurrent [mid - 1] [0] + originalToCurrent [mid - 1] [1];
else
return originalToCurrent [mid] [1];
else
return originalOffset - originalToCurrent [mid] [0] + originalToCurrent [mid] [1];
else
low = mid + 1;
} // while
return -1;
}
/**
* Returns source this snapshot has originally been created from.
*
* @return a source this snapshot has originally been created from.
*/
public Source getSource () {
return source;
}
@Override
public String toString () {
StringBuilder sb = new StringBuilder ("Snapshot ");
sb.append (hashCode ());
sb.append (": ");
Source _source = getSource ();
FileObject fileObject = _source.getFileObject ();
if (fileObject != null)
sb.append (fileObject.getNameExt ());
else
sb.append (mimePath).append (" ").append (_source.getDocument (false));
if (!getMimeType ().equals (_source.getMimeType ())) {
sb.append ("( ").append (getMimeType ()).append (" ");
sb.append (getOriginalOffset (0)).append ("-").append(getOriginalOffset (getText ().length () - 1)).append (")");
}
return sb.toString ();
}
private static final class CharSequenceView implements CharSequence {
private final CharSequence seq;
private final int from;
private final int to;
CharSequenceView(
@NonNull final CharSequence str,
final int from,
final int to) {
Parameters.notNull("str", str); //NOI18N
checkRanges(from, to, 0, str.length());
this.seq = str;
this.from = from;
this.to = to;
}
@Override
public int length() {
return to - from;
}
@Override
public char charAt(final int index) {
return seq.charAt(from+index);
}
@Override
public CharSequence subSequence(final int start, final int end) {
checkRanges(start, end, from, to);
return new CharSequenceView(seq, from+start, from+end);
}
private static void checkRanges(
final int from,
final int to,
final int lbound,
final int ubound) {
if (from < 0) {
throw new IllegalArgumentException("Start index: " + from + " < 0 "); //NOI18N
}
if (from > to) {
throw new IllegalArgumentException("Start index: " + from + " > To index:" + to); //NOI18N
}
if (from + lbound > ubound) {
throw new IllegalArgumentException("Start index: " + from + " > length: " + (ubound - lbound)); //NOI18N
}
if (to + lbound > ubound) {
throw new IllegalArgumentException("To index: " + to + " > length: " + (ubound - lbound)); //NOI18N
}
}
@Override
public String toString() {
if (seq instanceof String) {
return ((String)seq).substring(from, to);
} else {
final char[] data = new char[to-from];
for (int i=0; i
© 2015 - 2025 Weber Informatics LLC | Privacy Policy