com.oracle.truffle.api.source.Source Maven / Gradle / Ivy
Show all versions of truffle-api Show documentation
/*
* Copyright (c) 2013, 2018, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* The Universal Permissive License (UPL), Version 1.0
*
* Subject to the condition set forth below, permission is hereby granted to any
* person obtaining a copy of this software, associated documentation and/or
* data (collectively the "Software"), free of charge and under any and all
* copyright rights in the Software, and any and all patent rights owned or
* freely licensable by each licensor hereunder covering either (i) the
* unmodified Software as contributed to or provided by such licensor, or (ii)
* the Larger Works (as defined below), to deal in both
*
* (a) the Software, and
*
* (b) any piece of software and/or hardware listed in the lrgrwrks.txt file if
* one is included with the Software each a "Larger Work" to which the Software
* is contributed by such licensors),
*
* without restriction, including without limitation the rights to copy, create
* derivative works of, display, perform, and distribute the Software and make,
* use, sell, offer for sale, import, export, have made, and have sold the
* Software and the Larger Work(s), and to sublicense the foregoing rights on
* either these or other terms.
*
* This license is subject to the following condition:
*
* The above copyright notice and either this complete permission notice or at a
* minimum a reference to the UPL must be included in all copies or substantial
* portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.oracle.truffle.api.source;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
import java.nio.file.FileSystemNotFoundException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.spi.FileTypeDetector;
import java.util.Arrays;
import java.util.Collection;
import java.util.Objects;
import java.util.ServiceLoader;
import java.util.Set;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Engine;
import org.graalvm.polyglot.Language;
import org.graalvm.polyglot.io.ByteSequence;
import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.TruffleFile;
import com.oracle.truffle.api.TruffleOptions;
import com.oracle.truffle.api.impl.Accessor.EngineSupport;
import com.oracle.truffle.api.nodes.LanguageInfo;
/**
* Representation of a source code unit and its contents that can be evaluated in a language. Each
* source is associated with the the ID of the language.
*
* From a file on disk
*
* Each file is represented as a canonical object, indexed by the absolute, canonical path name of
* the file. File content is read eagerly and may be optionally cached. Sample
* usage:
*
* {@link SourceSnippets#fromFile}
*
* The starting point is {@link Source#newBuilder(String, TruffleFile)} method.
*
* Read from an URL
*
* One can read remote or in JAR resources using the {@link Source#newBuilder(String, java.net.URL)}
* factory:
*
* {@link SourceSnippets#fromURL}
*
* Each URL source is represented as a canonical object, indexed by the URL. Contents are read
* eagerly once the {@link SourceBuilder#build()} method is called.
*
* Source from a literal text
*
* An anonymous immutable code snippet can be created from a string via the
* {@link Source#newBuilder(String, CharSequence, String) } factory method:
*
* {@link SourceSnippets#fromAString}
*
* Reading from a stream
*
* If one has a {@link Reader} one can convert its content into a {@link Source} via
* {@link Source#newBuilder(String, java.io.Reader, String)} method:
*
* {@link SourceSnippets#fromReader}
*
* the content is read eagerly once the {@link SourceBuilder#build()} method is called.
*
* Reading from bytes
*
* Sources can be created from bytes. Please note that all character related methods will throw an
* {@link UnsupportedOperationException} if that is the case.
*
* Attributes
*
* The source object can be associated with various attributes like {@link #getName()} ,
* {@link #getURI() ()}, {@link #getMimeType()} and these are immutable. The system makes the best
* effort to derive values of these attributes from the location and/or content of the
* {@link Source} object. However, to give the user that creates the source control over these
* attributes, the API offers an easy way to alter values of these attributes.
*
* Character and binary based Sources
*
* A source is either {@link #hasBytes() byte} or {@link #hasCharacters() character} based. For
* literal sources it depends on whether the byte or character based factory method was used. When
* the source was loaded from a {@link File file} or {@link URL url} then the
* {@link LanguageInfo#getDefaultMimeType() default MIME type} of the provided language will be used
* to determine whether bytes or characters should be loaded. The behavior can be customized by
* specifying a {@link SourceBuilder#mimeType(String) MIME type} or
* {@link SourceBuilder#content(ByteSequence) content} explicitly. If the specified or inferred MIME
* type starts with 'text/
or the MIME types is null
then it will be
* interpreted as character based, otherwise byte based.
*
* @since 0.8 or earlier
*/
@SuppressWarnings("deprecation")
public abstract class Source {
private static final String UNKNOWN_MIME_TYPE = "content/unknown";
private static final Source EMPTY = new SourceImpl.Key(null, null, null, null, null, null, null, false, false, false, true).toSourceNotInterned();
private static final String NO_FASTPATH_SUBSOURCE_CREATION_MESSAGE = "do not create sub sources from compiled code";
private static final String URI_SCHEME = "truffle";
private static final int MAX_BUFFER_SIZE = Integer.MAX_VALUE - 8;
private static final int BUFFER_SIZE = 8192;
static final Class> BYTE_SEQUENCE_CLASS = ByteSequence.create(new byte[0]).getClass();
private static final InternedSources SOURCES = new InternedSources();
private volatile TextMap textMap;
private volatile URI computedURI;
volatile org.graalvm.polyglot.Source polyglotSource;
abstract Object getSourceId();
Source() {
}
/**
* Returns the language this source was created with. The string returned matches the
* {@link LanguageInfo#getId() id} of the language.
*
* @return the language of this source.
* @since 0.28
*/
public abstract String getLanguage();
/**
* Returns the name of this resource holding a guest language program. An example would be the
* name of a guest language source code file. Name is supposed to be shorter than
* {@link #getPath()}.
*
* @return the name of the guest language program
* @since 0.8 or earlier
*/
public abstract String getName();
/**
* The fully qualified name of the source. In case this source originates from a {@link File},
* then the default path is the normalized, {@link File#getCanonicalPath() canonical path}.
*
* @since 0.8 or earlier
*/
public abstract String getPath();
/**
* Check whether this source has been marked as internal, meaning that it has been
* provided by the infrastructure, language implementation, or system library. Internal
* sources are presumed to be irrelevant to guest language programmers, as well as possibly
* confusing and revealing of language implementation details.
*
* On the other hand, tools should be free to make internal sources visible in
* (possibly privileged) modes that are useful for language implementors.
*
* One can specify whether a source is internal when {@link SourceBuilder#internal(boolean)
* building it}.
*
* @return whether this source is marked as internal
* @since 0.15
*/
public abstract boolean isInternal();
/**
* Returns true
if code caching is enabled for this source. If true
* then the source does not require parsing every time this source is evaluated. If
* false
then the source requires parsing every time the source is evaluated but
* does not remember any state. Disabling caching may be useful if the source is known to only
* be evaluated once.
*
* @return whether this source is allowed to be cached
* @since 1.0
*/
public abstract boolean isCached();
/**
* Check whether this source has been marked as interactive. Interactive sources are
* provided by an entity which is able to interactively read output and provide an input during
* the source execution; that can be a user I/O through an interactive shell for instance.
*
* Depending on {@link com.oracle.truffle.api.TruffleLanguage.Registration#interactive()
* language interactive} capability, when interactive sources are executed, the
* appropriate result can be passed directly to the environment
* {@link com.oracle.truffle.api.TruffleLanguage.Env#out() output stream} or
* {@link com.oracle.truffle.api.TruffleLanguage.Env#err() error stream} and
* {@link com.oracle.truffle.api.TruffleLanguage.Env#in() input stream} can be used to read user
* input during the execution, to clarify the execution behavior by asking questions for
* instance. Non-interactive languages are expected to ignore this property.
*
* One can specify whether a source is interactive when
* {@link SourceBuilder#interactive(boolean) building it}.
*
* @return whether this source is marked as interactive
* @since 0.21
*/
public abstract boolean isInteractive();
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public final boolean equals(Object obj) {
if (!(obj instanceof Source)) {
return false;
}
return getSourceId().equals(((Source) obj).getSourceId());
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public final int hashCode() {
return getSourceId().hashCode();
}
/**
* Creates a {@linkplain Source Source instance} that represents the contents of a sub-range of
* an this
{@link Source}.
*
* @param baseCharIndex 0-based index of the first character of the sub-range
* @param length the number of characters in the sub-range
* @return a new instance representing a sub-range of another Source
* @throws IllegalArgumentException if the specified sub-range is not contained in the base
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 0.15
*/
public Source subSource(int baseCharIndex, int length) {
if (hasBytes()) {
throw new UnsupportedOperationException("Operation is only enabled for character based sources.");
}
CompilerAsserts.neverPartOfCompilation(NO_FASTPATH_SUBSOURCE_CREATION_MESSAGE);
return SubSourceImpl.create(this, baseCharIndex, length);
}
/**
* Returns all characters of the source.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 0.28
*/
public abstract CharSequence getCharacters();
/**
* Returns true
if this source represents a byte based source, else
* false
. A source is either a byte based or a character based source, never both
* at the same time.
*
* The method {@link #getBytes()} is only supported if this method returns true
.
*
* @see #getBytes()
* @since 1.0
*/
public abstract boolean hasBytes();
/**
* Returns true
if this source represents a character based source, else
* false
. A source is either a byte based or a character based source, never both
* at the same time.
*
*
* The following methods are only supported if {@link #hasCharacters()} is true
:
*
* - {@link #getReader()}
*
- {@link #getCharacters()}
*
- {@link #getLineCount()}
*
- {@link #getLineNumber(int)}
*
- {@link #getColumnNumber(int)}
*
- {@link #getLineStartOffset(int)}
*
- {@link #getLineLength(int)}
*
*
* @since 1.0
*/
public abstract boolean hasCharacters();
/**
* Returns the bytes of the source if it is a {@link #hasBytes() byte based source}.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasBytes() bytes}
* .
* @see #hasBytes()
* @since 1.0
*/
public abstract ByteSequence getBytes();
/**
* The URL if the source is retrieved via URL.
*
* @return URL or null
* @since 0.8 or earlier
*/
public abstract URL getURL();
abstract URI getOriginalURI();
/**
* Get URI of the source. Every source has an associated {@link URI}, which can be used as a
* persistent identification of the source. For example one can
* {@link com.oracle.truffle.api.debug.DebuggerSession#install(com.oracle.truffle.api.debug.Breakpoint)
* register a breakpoint using a URI} to a source that isn't loaded yet and it will be activated
* when the source is evaluated. The {@link URI} returned by this method should be as unique as
* possible, yet it can happen that different {@link Source sources} return the same
* {@link #getURI} - for example when content of a {@link Source#newBuilder(String, TruffleFile)
* file on a disk} changes and is re-loaded.
*
* @return a URI, it's never null
* @since 0.14
*/
public final URI getURI() {
URI uri = getOriginalURI();
if (uri == null) {
uri = computedURI;
if (uri == null) {
byte[] bytes = hasBytes() ? getBytes().toByteArray() : getCharacters().toString().getBytes();
uri = computedURI = getNamedURI(getName(), bytes);
}
}
return uri;
}
/**
* Returns the MIME type that is associated with this source. Sources have a null
* MIME type by default. If the MIME type is null
then the
* {@link Language#getDefaultMimeType() default MIME type} of the language will be used to
* interpret the source. If set explicitly then the language needs to
* {@link Language#getMimeTypes() support} the MIME type in order to {@link Context#eval(Source)
* evaluate} the source. If not null
the MIME type is already verified containing
* no spaces and a '/' between group and sub-group. An example for a valid MIME type is:
* text/javascript
.
*
* The MIME type can be guessed by the system based on {@link #findMimeType(TruffleFile) files}
* or {@link #findMimeType(URL) urls}
*
* @see LanguageInfo#getDefaultMimeType()
* @see LanguageInfo#getMimeTypes()
* @see Source#findMimeType(TruffleFile)
* @see Source#findMimeType(URL)
* @return MIME type of this source or null
, if not explicitly set.
* @since 1.0
*/
public abstract String getMimeType();
/**
* Access to the source contents.
*
* @since 0.8 or earlier
*/
public final Reader getReader() {
return new CharSequenceReader(getCharacters());
}
/**
* @since 0.8 or earlier
* @deprecated without replacement use either {@link #getBytes()} or {@link #getCharacters()}
* directly.
*/
@Deprecated
public final InputStream getInputStream() {
return new ByteArrayInputStream(getCharacters().toString().getBytes());
}
/**
* Gets the number of characters or bytes of the source.
*
* @since 0.8
*/
public final int getLength() {
return getTextMap().length();
}
/**
* Gets the text (not including a possible terminating newline) in a (1-based) numbered line.
* Causes the contents of this source to be loaded if they are loaded lazily.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 0.28
*/
public final CharSequence getCharacters(int lineNumber) {
final int offset = getTextMap().lineStartOffset(lineNumber);
final int length = getTextMap().lineLength(lineNumber);
return getCharacters().subSequence(offset, offset + length);
}
/**
* The number of text lines in the source, including empty lines; characters at the end of the
* source without a terminating newline count as a line. Causes the contents of this source to
* be loaded if they are loaded lazily.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 0.8 or earlier
*/
public final int getLineCount() {
return getTextMap().lineCount();
}
/**
* Given a 0-based character offset, return the 1-based number of the line that includes the
* position. Causes the contents of this source to be loaded if they are loaded lazily.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if the offset is outside the text contents
* @since 0.8 or earlier
*/
public final int getLineNumber(int offset) throws IllegalArgumentException {
return getTextMap().offsetToLine(offset);
}
/**
* Given a 0-based character offset, return the 1-based number of the column at the position.
* Causes the contents of this source to be loaded if they are loaded lazily.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if the offset is outside the text contents
* @since 0.8 or earlier
*/
public final int getColumnNumber(int offset) throws IllegalArgumentException {
return getTextMap().offsetToCol(offset);
}
/**
* Given a 1-based line number, return the 0-based offset of the first character in the line.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if there is no such line in the text
* @since 0.8 or earlier
*/
public final int getLineStartOffset(int lineNumber) throws IllegalArgumentException {
return getTextMap().lineStartOffset(lineNumber);
}
/**
* The number of characters (not counting a possible terminating newline) in a (1-based)
* numbered line. Causes the contents of this source to be loaded if they are loaded lazily.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if there is no such line in the text
* @since 0.8 or earlier
*/
public final int getLineLength(int lineNumber) throws IllegalArgumentException {
return getTextMap().lineLength(lineNumber);
}
/**
* Returns an unavailable source section indicating that the source location is not available.
* Unavailable source sections have the same characteristics as empty source sections with
* character index 0
, but returns false
for
* {@link SourceSection#isAvailable()}. Unavailable source sections may be creatd for character
* and byte based sources.
*
* @see SourceSection#isAvailable()
* @since 0.18
*/
public final SourceSection createUnavailableSection() {
return new SourceSection(this, 0, -1);
}
/**
* Creates a representation of a line of text in the source identified only by line number, from
* which the character information will be computed. Please note that calling this method does
* cause the {@link Source#getCharacters() code} of this source to be loaded.
*
* @param lineNumber 1-based line number of the first character in the section
* @return newly created object representing the specified line
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if the given lineNumber does not exist the source
* @since 0.17
*/
public final SourceSection createSection(int lineNumber) {
if (hasBytes()) {
throw new UnsupportedOperationException("Operation is only enabled for character based sources.");
}
if (lineNumber < 1) {
throw new IllegalArgumentException("lineNumber < 1");
}
final int charIndex = getTextMap().lineStartOffset(lineNumber);
final int length = getTextMap().lineLength(lineNumber);
SourceSection section = new SourceSection(this, charIndex, length);
assert assertValid(section);
return section;
}
/**
* Creates a representation of a contiguous region of text in the source. Please note that
* calling this method does only cause the {@link Source#getCharacters() code} of this source to
* be loaded if assertions enabled. The bounds of the source section are only verified if
* assertions (-ea) are enabled in the host system. An {@link IllegalArgumentException} is
* thrown if the given indices are out of bounds of the source bounds.
*
* @param charIndex 0-based position of the first character in the section
* @param length the number of characters in the section
* @return newly created object representing the specified region
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if charIndex < 0 or length < 0; in case assertions are
* enabled also if the given bounds are out of the source bounds.
* @since 0.17
*/
public final SourceSection createSection(int charIndex, int length) {
if (hasBytes()) {
throw new UnsupportedOperationException("Operation is only enabled for character based sources.");
}
if (charIndex < 0) {
throw new IllegalArgumentException("charIndex < 0");
} else if (length < 0) {
throw new IllegalArgumentException("length < 0");
}
SourceSection section = new SourceSection(this, charIndex, length);
assert assertValid(section);
return section;
}
/**
* Creates a representation of a contiguous region of text in the source. Computes the
* {@code charIndex} value by building a text map of lines in the source. Please note that
* calling this method does cause the {@link Source#getCharacters() code} of this source to be
* loaded.
*
* @param startLine 1-based line number of the first character in the section
* @param startColumn 1-based column number of the first character in the section
* @param length the number of characters in the section
* @return newly created object representing the specified region
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @throws IllegalArgumentException if arguments are outside the text of the source bounds
* @see #createSection(int, int)
* @since 0.17
*/
public final SourceSection createSection(int startLine, int startColumn, int length) {
if (hasBytes()) {
throw new UnsupportedOperationException("Operation is only enabled for character based sources.");
}
if (startLine <= 0) {
throw new IllegalArgumentException("startLine < 1");
} else if (startColumn <= 0) {
throw new IllegalArgumentException("startColumn < 1");
} else if (length < 0) {
throw new IllegalArgumentException("length < 0");
}
final int lineStartOffset = getTextMap().lineStartOffset(startLine);
if (startColumn > getTextMap().lineLength(startLine)) {
throw new IllegalArgumentException("column out of range");
}
final int charIndex = lineStartOffset + startColumn - 1;
if (charIndex + length > getCharacters().length()) {
throw new IllegalArgumentException("charIndex out of range");
}
SourceSection section = new SourceSection(this, charIndex, length);
assert assertValid(section);
return section;
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public String toString() {
return "Source [language=" + getLanguage() + ", name=" + getName() + ", path=" + getPath() + ", internal=" + isInternal() + ", cached=" + isCached() +
", interactive=" + isInteractive() + ", hasBytes=" + hasBytes() + ", hasCharacters=" + hasCharacters() + ", URL=" + getURL() + ", URI=" + getURI() +
", mimeType=" + getMimeType() + "]";
}
private static boolean assertValid(SourceSection section) {
if (!section.isValid()) {
throw new IllegalArgumentException("Invalid source section bounds.");
}
return true;
}
abstract boolean isLegacy();
abstract Source copy();
final TextMap getTextMap() {
if (hasBytes()) {
throw new UnsupportedOperationException("Operation is only enabled for character based sources.");
}
TextMap res = textMap;
if (res == null) {
res = textMap = createTextMap();
}
assert res != null;
return res;
}
TextMap createTextMap() {
final CharSequence code = getCharacters();
if (code == null) {
throw new RuntimeException("can't read file " + getName());
}
return TextMap.fromCharSequence(code);
}
private URI getNamedURI(String name, byte[] bytes) {
return getNamedURI(name, bytes, 0, bytes.length);
}
private URI getNamedURI(String name, byte[] bytes, int byteIndex, int length) {
String digest;
if (bytes != null) {
digest = digest(bytes, byteIndex, length);
} else {
digest = Integer.toString(System.identityHashCode(this), 16);
}
if (name != null) {
digest += '/' + name;
}
try {
return new URI(URI_SCHEME, digest, null);
} catch (URISyntaxException ex) {
throw new Error(ex); // Should not happen
}
}
/**
* Creates a new character based source from a character sequence. The given characters must not
* mutate after they were accessed for the first time.
*
* Example usage: {@link SourceSnippets#fromAString}
*
* @param language the language id, must not be null
* @param characters the character sequence or string, must not be null
* @param name the name of the source, if null
then "Unnamed"
will be
* used as name.
* @since 1.0
*/
public static LiteralBuilder newBuilder(String language, CharSequence characters, String name) {
return EMPTY.new LiteralBuilder(language, characters).name(name);
}
/**
* Creates a new byte based source from a byte sequence. The given bytes must not mutate after
* they were accessed for the first time.
*
* Example usage: {@link SourceSnippets#fromBytes}
*
* @param language the language id, must not be null
* @param bytes the byte sequence or string, must not be null
* @param name the name of the source, if null
then "Unnamed"
will be
* used as name.
* @since 1.0
*/
public static LiteralBuilder newBuilder(String language, ByteSequence bytes, String name) {
return EMPTY.new LiteralBuilder(language, bytes).name(name);
}
/**
* Creates a new file based source builder from a given file. A file based source is either
* interpreted as {@link Source#hasBytes() binary} or {@link Source#hasCharacters() character}
* source depending on the {@link LanguageInfo#getDefaultMimeType() default MIME type} of the
* language, the {@link SourceBuilder#content(ByteSequence) contents} or the specified
* {@link SourceBuilder#mimeType(String) MIME type}. A language may be detected from an existing
* file using {@link #findLanguage(TruffleFile)}.
*
* Example usage: {@link SourceSnippets#fromFile}
*
* @param language the language id, must not be null
* @param file the file to use and load, must not be null
* @since 1.0
*/
public static SourceBuilder newBuilder(String language, TruffleFile file) {
return newBuilder(language, SourceAccessor.asFile(file));
}
/*
* Internal constructor only for polyglot sources.
*/
static SourceBuilder newBuilder(String language, File source) {
return EMPTY.new LiteralBuilder(language, source);
}
/**
* Creates a new URL based source builder from a given URL. A URL based source is either
* interpreted as {@link Source#hasBytes() binary} or {@link Source#hasCharacters() character}
* source depending on the {@link LanguageInfo#getDefaultMimeType() default MIME type} of the
* language, the {@link SourceBuilder#content(ByteSequence) contents} or the specified
* {@link SourceBuilder#mimeType(String) MIME type}. A language may be detected from an existing
* file using {@link #findLanguage(URL)}.
*
* Example usage: {@link SourceSnippets#fromURL}
*
* @param language the language id, must not be null
* @param url the URL to use and load, must not be null
* @since 1.0
*/
public static SourceBuilder newBuilder(String language, URL url) {
return EMPTY.new LiteralBuilder(language, url);
}
/**
* Creates new character based source from a reader.
*
*
* Example usage: {@link SourceSnippets#fromReader}
*
* @since 1.0
*/
public static SourceBuilder newBuilder(String language, Reader source, String name) {
return EMPTY.new LiteralBuilder(language, source).name(name);
}
/**
* @since 0.15
* @deprecated use {@link #newBuilder(String, TruffleFile, String)}
*/
@Deprecated
public static Builder newBuilder(File file) {
return EMPTY.new Builder<>(file);
}
/**
* @since 0.15
* @deprecated use {@link #newBuilder(String, CharSequence, String)}
*/
@Deprecated
public static Builder newBuilder(String text) {
return newBuilder((CharSequence) text);
}
/**
* @since 0.28
* @deprecated use {@link #newBuilder(String, CharSequence, String)}
*/
@Deprecated
public static Builder newBuilder(CharSequence characters) {
return EMPTY.new Builder<>(characters);
}
/**
* @since 0.15
* @deprecated use {@link #newBuilder(String, URL)}
*/
@Deprecated
public static Builder newBuilder(URL url) {
return EMPTY.new Builder<>(url);
}
/**
* @since 0.15
* @deprecated use {@link #newBuilder(String, Reader, String)}
*/
@Deprecated
public static Builder newBuilder(Reader reader) {
return EMPTY.new Builder<>(reader);
}
/**
* Returns the first language that supports evaluating the probed mime type of a given
* {@link File file}. This method is a shortcut for:
*
*
*
* String mimeType = Source.findMimeType(file);
* return mimeType != null ? Source.findLanguage(mimeType) : null;
*
*
*
* @param file the file to find the first language for
* @throws IOException if an error opening the file occurred.
* @see #findMimeType(URL)
* @see #findLanguage(String)
* @since 1.0
*/
public static String findLanguage(TruffleFile file) throws IOException {
return findLanguage(findMimeType(file));
}
/**
* Returns the first language that supports evaluating the probed mime type of a given
* {@link URL URL}. This method is a shortcut for:
*
*
*
* String mimeType = Source.findMimeType(url);
* return mimeType != null ? Source.findLanguage(mimeType) : null;
*
*
*
* @param url the url to find the first language for
* @throws IOException if an error opening the url occurred.
* @see #findMimeType(URL)
* @see #findLanguage(String)
* @since 1.0
*/
public static String findLanguage(URL url) throws IOException {
return findLanguage(findMimeType(url));
}
/**
* Returns the probed MIME type for a given file, or null
if no MIME type could be
* resolved. Typically the MIME type is identified using the file extension and/or using its
* contents. Probing the MIME type of an {@link TruffleFile} may require to opening the file.
*
* @throws IOException if an error opening the file occurred.
* @see #findLanguage(TruffleFile)
* @since 1.0
*/
public static String findMimeType(TruffleFile file) throws IOException {
return findMimeType(SourceAccessor.asFile(file).toPath(), null);
}
/**
* Returns the probed MIME type for a given url, or null
if no MIME type could be
* resolved. Typically the MIME type is identified using the file extension, connection
* meta-data and/or using it contents. Returns null
if the language of the given
* file could not be detected. Probing the language of an URL may require to open a new URL
* connection.
*
* @throws IOException if an error opening the url occurred.
* @see #findLanguage(URL)
* @since 1.0
*/
public static String findMimeType(URL url) throws IOException {
return findMimeType(url, url.openConnection(), null);
}
/**
* Returns the first installed language that supports evaluating sources for a given MIME type.
* Returns null
if no language was found that supports a given MIME type. The
* languages are queried in the same order as returned by {@link Engine#getLanguages()}.
*
* @since 1.0
*/
public static String findLanguage(String mimeType) {
return org.graalvm.polyglot.Source.findLanguage(mimeType);
}
private static IllegalArgumentException invalidMimeType() {
return new IllegalArgumentException("Invalid MIME type provided. MIME types consist of a type and a subtype separated by '/'.");
}
private static final int[] S = new int[]{
0x29, 0x2E, 0x43, 0xC9, 0xA2, 0xD8, 0x7C, 0x01, 0x3D, 0x36, 0x54, 0xA1, 0xEC, 0xF0, 0x06, 0x13,
0x62, 0xA7, 0x05, 0xF3, 0xC0, 0xC7, 0x73, 0x8C, 0x98, 0x93, 0x2B, 0xD9, 0xBC, 0x4C, 0x82, 0xCA,
0x1E, 0x9B, 0x57, 0x3C, 0xFD, 0xD4, 0xE0, 0x16, 0x67, 0x42, 0x6F, 0x18, 0x8A, 0x17, 0xE5, 0x12,
0xBE, 0x4E, 0xC4, 0xD6, 0xDA, 0x9E, 0xDE, 0x49, 0xA0, 0xFB, 0xF5, 0x8E, 0xBB, 0x2F, 0xEE, 0x7A,
0xA9, 0x68, 0x79, 0x91, 0x15, 0xB2, 0x07, 0x3F, 0x94, 0xC2, 0x10, 0x89, 0x0B, 0x22, 0x5F, 0x21,
0x80, 0x7F, 0x5D, 0x9A, 0x5A, 0x90, 0x32, 0x27, 0x35, 0x3E, 0xCC, 0xE7, 0xBF, 0xF7, 0x97, 0x03,
0xFF, 0x19, 0x30, 0xB3, 0x48, 0xA5, 0xB5, 0xD1, 0xD7, 0x5E, 0x92, 0x2A, 0xAC, 0x56, 0xAA, 0xC6,
0x4F, 0xB8, 0x38, 0xD2, 0x96, 0xA4, 0x7D, 0xB6, 0x76, 0xFC, 0x6B, 0xE2, 0x9C, 0x74, 0x04, 0xF1,
0x45, 0x9D, 0x70, 0x59, 0x64, 0x71, 0x87, 0x20, 0x86, 0x5B, 0xCF, 0x65, 0xE6, 0x2D, 0xA8, 0x02,
0x1B, 0x60, 0x25, 0xAD, 0xAE, 0xB0, 0xB9, 0xF6, 0x1C, 0x46, 0x61, 0x69, 0x34, 0x40, 0x7E, 0x0F,
0x55, 0x47, 0xA3, 0x23, 0xDD, 0x51, 0xAF, 0x3A, 0xC3, 0x5C, 0xF9, 0xCE, 0xBA, 0xC5, 0xEA, 0x26,
0x2C, 0x53, 0x0D, 0x6E, 0x85, 0x28, 0x84, 0x09, 0xD3, 0xDF, 0xCD, 0xF4, 0x41, 0x81, 0x4D, 0x52,
0x6A, 0xDC, 0x37, 0xC8, 0x6C, 0xC1, 0xAB, 0xFA, 0x24, 0xE1, 0x7B, 0x08, 0x0C, 0xBD, 0xB1, 0x4A,
0x78, 0x88, 0x95, 0x8B, 0xE3, 0x63, 0xE8, 0x6D, 0xE9, 0xCB, 0xD5, 0xFE, 0x3B, 0x00, 0x1D, 0x39,
0xF2, 0xEF, 0xB7, 0x0E, 0x66, 0x58, 0xD0, 0xE4, 0xA6, 0x77, 0x72, 0xF8, 0xEB, 0x75, 0x4B, 0x0A,
0x31, 0x44, 0x50, 0xB4, 0x8F, 0xED, 0x1F, 0x1A, 0xDB, 0x99, 0x8D, 0x33, 0x9F, 0x11, 0x83, 0x14
};
static Source buildSource(String language, Object origin, String name, String mimeType, Object content, URI uri,
boolean internal, boolean interactive, boolean cached, boolean legacy) throws IOException {
String useName = name;
URI useUri = uri;
Object useContent = content;
String useMimeType = mimeType;
String usePath = null;
URL useUrl = null;
if (origin instanceof File) {
final File file = (File) origin;
File absoluteFile = file.exists() ? file.getCanonicalFile() : file;
useName = useName == null ? file.getName() : useName;
usePath = usePath == null ? absoluteFile.getPath() : usePath;
useUri = useUri == null ? absoluteFile.toPath().toUri() : useUri;
useMimeType = useMimeType == null ? findMimeType(absoluteFile.toPath(), getValidMimeTypes(language)) : useMimeType;
if (legacy) {
useMimeType = useMimeType == null ? UNKNOWN_MIME_TYPE : useMimeType;
useContent = useContent == null ? read(file) : useContent;
} else {
if (useContent == null) {
if (isCharacterBased(language, useMimeType)) {
useContent = read(file);
} else {
useContent = ByteSequence.create(readBytes(file));
}
}
}
} else if (origin instanceof Reader) {
final Reader r = (Reader) origin;
useContent = useContent == null ? read(r) : useContent;
} else if (origin instanceof URL) {
final URL url = (URL) origin;
String urlPath = url.getPath();
int lastIndex = urlPath.lastIndexOf('/');
useName = useName == null && lastIndex != -1 ? url.getPath().substring(lastIndex + 1) : useName;
// avoid opening connection twice for guessing the MIME type
if (useUri == null) {
try {
useUri = url.toURI();
} catch (URISyntaxException ex) {
throw new IOException("Bad URL: " + url, ex);
}
}
usePath = usePath == null ? url.toExternalForm() : usePath;
URLConnection connection = url.openConnection();
useMimeType = useMimeType == null ? findMimeType(url, connection, getValidMimeTypes(language)) : useMimeType;
if (legacy) {
useMimeType = useMimeType == null ? UNKNOWN_MIME_TYPE : useMimeType;
useContent = useContent == null ? read(new InputStreamReader(connection.getInputStream())) : useContent;
} else {
if (useContent == null) {
if (isCharacterBased(language, useMimeType)) {
useContent = read(new InputStreamReader(connection.getInputStream()));
} else {
useContent = ByteSequence.create(readBytes(connection));
}
}
}
} else if (origin instanceof ByteSequence) {
useContent = useContent == null ? origin : useContent;
} else {
assert origin instanceof CharSequence;
useContent = useContent == null ? origin : useContent;
}
if (!legacy && useName == null) {
useName = "Unnamed";
}
useContent = enforceInterfaceContracts(useContent);
SourceImpl.Key key = new SourceImpl.Key(useContent, useMimeType, language, useUrl, useUri, useName, usePath, internal, interactive, cached, legacy);
return SOURCES.intern(key);
}
static byte[] readBytes(File file) throws IOException {
return SourceAccessor.isTruffleFile(file) ? SourceAccessor.readTruffleFile(file) : Files.readAllBytes(file.toPath());
}
static byte[] readBytes(URLConnection connection) throws IOException {
long size = connection.getContentLengthLong();
if (size < 0) {
size = BUFFER_SIZE;
} else {
if (size > Integer.MAX_VALUE) {
throw new OutOfMemoryError("Too many bytes.");
}
}
return readBytes(connection.getInputStream(), (int) size);
}
/*
* Copied from Files#read
*/
private static byte[] readBytes(InputStream source, int initialSize) throws IOException {
int capacity = initialSize;
byte[] buf = new byte[capacity];
int nread = 0;
int n;
for (;;) {
// read to EOF which may read more or less than initialSize (eg: file
// is truncated while we are reading)
while ((n = source.read(buf, nread, capacity - nread)) > 0) {
nread += n;
}
// if last call to source.read() returned -1, we are done
// otherwise, try to read one more byte; if that failed we're done too
if (n < 0 || (n = source.read()) < 0) {
break;
}
// one more byte was read; need to allocate a larger buffer
if (capacity <= MAX_BUFFER_SIZE - capacity) {
capacity = Math.max(capacity << 1, BUFFER_SIZE);
} else {
if (capacity == MAX_BUFFER_SIZE) {
throw new OutOfMemoryError("Required array size too large");
}
capacity = MAX_BUFFER_SIZE;
}
buf = Arrays.copyOf(buf, capacity);
buf[nread++] = (byte) n;
}
return (capacity == nread) ? buf : Arrays.copyOf(buf, nread);
}
static String read(File file) throws IOException {
return new String(readBytes(file), StandardCharsets.UTF_8);
}
static String read(Reader reader) throws IOException {
final StringBuilder builder = new StringBuilder();
final char[] buffer = new char[1024];
try {
while (true) {
final int n = reader.read(buffer);
if (n == -1) {
break;
}
builder.append(buffer, 0, n);
}
} finally {
reader.close();
}
return builder.toString();
}
static String digest(byte[] message, int from, int length) {
int[] m = new int[19];
int[] x = new int[48];
int[] c = new int[16];
int t;
int loop = 1;
int start = 0;
int bytes = 0;
for (int i = 0; i < 16; ++i) {
x[i] = c[i] = 0;
}
int last = 0;
int index = from;
m[16] = m[17] = m[18] = 0;
while (loop == 1) {
m[0] = m[16];
m[1] = m[17];
m[2] = m[18];
for (int i = 3; i < 16; i++) {
m[i] = 0;
}
int i;
for (i = start; index < length && i < 16; ++index) {
int code = message[index];
if (code < 0) {
code += 256;
}
m[i++] = code;
}
bytes += i - start;
start = i - 16;
if (index == length && i < 16) {
loop = 2;
t = 16 - (bytes & 15);
for (; i < 16; ++i) {
m[i] = t;
}
}
for (i = 0; i < 16; ++i) {
c[i] ^= S[m[i] ^ last];
last = c[i];
}
for (i = 0; i < loop; ++i) {
int[] mOrC = i == 0 ? m : c;
x[16] = mOrC[0];
x[32] = x[16] ^ x[0];
x[17] = mOrC[1];
x[33] = x[17] ^ x[1];
x[18] = mOrC[2];
x[34] = x[18] ^ x[2];
x[19] = mOrC[3];
x[35] = x[19] ^ x[3];
x[20] = mOrC[4];
x[36] = x[20] ^ x[4];
x[21] = mOrC[5];
x[37] = x[21] ^ x[5];
x[22] = mOrC[6];
x[38] = x[22] ^ x[6];
x[23] = mOrC[7];
x[39] = x[23] ^ x[7];
x[24] = mOrC[8];
x[40] = x[24] ^ x[8];
x[25] = mOrC[9];
x[41] = x[25] ^ x[9];
x[26] = mOrC[10];
x[42] = x[26] ^ x[10];
x[27] = mOrC[11];
x[43] = x[27] ^ x[11];
x[28] = mOrC[12];
x[44] = x[28] ^ x[12];
x[29] = mOrC[13];
x[45] = x[29] ^ x[13];
x[30] = mOrC[14];
x[46] = x[30] ^ x[14];
x[31] = mOrC[15];
x[47] = x[31] ^ x[15];
t = 0;
for (int j = 0; j < 18; ++j) {
for (int k = 0; k < 48; ++k) {
x[k] = t = x[k] ^ S[t];
}
t = (t + j) & 0xFF;
}
}
}
StringBuilder result = new StringBuilder(32);
for (int i = 0; i < 16; ++i) {
final String hex = Integer.toHexString(x[i]);
if (result.length() == 0) {
if (hex.equals("0")) {
continue;
}
} else {
if (hex.length() == 1) {
result.append("0");
}
}
result.append(hex);
}
return result.toString();
}
@SuppressWarnings({"unchecked", "unused"})
static E raise(Class type, Exception ex) throws E {
throw (E) ex;
}
@SuppressWarnings("all")
static Object enforceInterfaceContracts(Object sequence) {
boolean assertions = false;
assert assertions = true;
if (assertions) {
if (sequence instanceof CharSequence) {
return enforceCharSequenceContracts((CharSequence) sequence);
} else {
assert sequence instanceof ByteSequence;
return enforceByteSequenceContracts((ByteSequence) sequence);
}
}
return sequence;
}
static ByteSequence enforceByteSequenceContracts(ByteSequence sequence) {
if (BYTE_SEQUENCE_CLASS.isInstance(sequence)) {
return sequence;
} else if (sequence instanceof ByteSequenceWrapper) {
// already wrapped
return sequence;
} else {
return new ByteSequenceWrapper(sequence);
}
}
static CharSequence enforceCharSequenceContracts(CharSequence sequence) {
if (sequence instanceof String) {
return sequence;
} else if (sequence instanceof CharSequenceWrapper) {
// already wrapped
return sequence;
} else {
return new CharSequenceWrapper(sequence);
}
}
static String findMimeType(final Path filePath, Set validMimeTypes) throws IOException {
if (!TruffleOptions.AOT) {
Collection loaders = SourceAccessor.allLoaders();
for (ClassLoader l : loaders) {
for (FileTypeDetector detector : ServiceLoader.load(FileTypeDetector.class, l)) {
String mimeType = detector.probeContentType(filePath);
if (mimeType != null && (validMimeTypes == null || validMimeTypes.contains(mimeType))) {
return mimeType;
}
}
}
}
String contentType = Files.probeContentType(filePath);
if (contentType != null && (validMimeTypes == null || validMimeTypes.contains(contentType))) {
return contentType;
}
return null;
}
static String findMimeType(final URL url, URLConnection connection, Set validMimeTypes) throws IOException {
Path path;
try {
path = Paths.get(url.toURI());
String firstGuess = findMimeType(path, validMimeTypes);
if (firstGuess != null) {
return firstGuess;
}
} catch (URISyntaxException | IllegalArgumentException | FileSystemNotFoundException ex) {
// swallow and go on
}
String contentType = connection.getContentType();
if (contentType != null && (validMimeTypes == null || validMimeTypes.contains(contentType))) {
return contentType;
}
return null;
}
static boolean isCharacterBased(String language, String mimeType) {
if (language == null) {
return true;
}
EngineSupport support = SourceAccessor.ACCESSOR.engineSupport();
if (support == null) {
return true;
}
return support.isCharacterBasedSource(language, mimeType);
}
static Set getValidMimeTypes(String language) {
EngineSupport support = SourceAccessor.ACCESSOR.engineSupport();
if (support == null) {
return null;
}
return support.getValidMimeTypes(language);
}
private static void validateMimeType(String mimeType) {
if (mimeType == null) {
return;
}
int index = mimeType.indexOf('/');
if (index == -1 || index == 0 || index == mimeType.length() - 1) {
throw invalidMimeType();
}
if (mimeType.indexOf('/', index + 1) != -1) {
throw invalidMimeType();
}
}
@SuppressWarnings({"unchecked", "unused"})
static RuntimeException silenceException(Class type, Exception ex) throws E {
throw (E) ex;
}
/**
* Allows one to specify additional attribute before {@link #build() creating} new
* {@link Source} instance.
*
* To load a source from disk one can use:
*
* {@link SourceSnippets#fromFile}
*
* To load source from a {@link URL} one can use:
*
* {@link SourceSnippets#fromURL}
*
* To create a source representing characters:
*
* {@link SourceSnippets#fromAString}
*
* or read a source from a {@link Reader}:
*
* {@link SourceSnippets#fromReader}
*
* To create a source representing bytes:
*
* {@link SourceSnippets#fromBytes}
*
* Once your builder is configured, call {@link #build()} to perform the loading and
* construction of new {@link Source}.
*
* @since 1.0
*/
public class SourceBuilder {
private final String language;
private final Object origin;
private URI uri;
private String name;
private String mimeType;
private Object content;
private boolean internal;
private boolean interactive;
private boolean cached = true;
SourceBuilder(String language, Object origin) {
Objects.requireNonNull(language);
Objects.requireNonNull(origin);
this.language = language;
this.origin = origin;
}
/**
* Specifies a name to the {@link #build() to-be-built} {@link Source}.
*
* @param newName name that replaces the previously given one. If set to null
* then "Unnamed"
will be used.
* @return instance of this
builder
* @since 1.0
*/
public SourceBuilder name(String newName) {
this.name = newName;
return this;
}
/**
* Specifies character based content of {@link #build() to-be-built} {@link Source}. Using
* this method one can ignore the real content of a file or URL and use already read one, or
* completely different one. The given characters must not mutate after they were accessed
* for the first time. Example:
*
* {@link SourceSnippets#fromURLWithOwnContent}
*
* @param characters the code to be available via {@link Source#getCharacters()}
* @return instance of this builder - which's {@link #build()} method no longer throws an
* {@link IOException}
* @since 1.0
*/
public LiteralBuilder content(CharSequence characters) {
this.content = characters;
return (LiteralBuilder) this;
}
/**
* Specifies byte based content of {@link #build() to-be-built} {@link Source}. Using this
* method one can ignore the real content of a file or URL and use already read one, or
* completely different one. The given bytes must not mutate after they were accessed for
* the first time. Example:
*
* {@link SourceSnippets#fromURLWithOwnContent}
*
* @param bytes the code to be available via {@link Source#getBytes()}
* @return instance of this builder - which's {@link #build()} method no longer throws an
* {@link IOException}
* @since 1.0
*/
public LiteralBuilder content(ByteSequence bytes) {
this.content = bytes;
return (LiteralBuilder) this;
}
/**
* Explicitly assigns a {@link Source#getMimeType() MIME type} to the {@link #build()
* to-be-built} {@link Source}. If the MIME type is null
then the
* {@link Language#getDefaultMimeType() default MIME type} of the language will be used to
* interpret the source. If set explicitly then the language needs to
* {@link Language#getMimeTypes() support} the MIME type in order to
* {@link com.oracle.truffle.api.TruffleLanguage.Env#parse(Source, String...) parse} a
* source. If not null
then the MIME type will be verified containing no spaces
* and a '/' between group and sub-group. An example for a valid MIME type is:
* text/javascript
.
*
* The MIME type can be guessed by the system based on {@link #findMimeType(TruffleFile)
* files} or {@link #findMimeType(URL) urls}.
*
* @see LanguageInfo#getDefaultMimeType()
* @see LanguageInfo#getMimeTypes()
* @see Source#findMimeType(TruffleFile)
* @see Source#findMimeType(URL)
* @param mimeType the new mime type to be assigned, or null
if default MIME
* type should be used.
* @return instance of this
builder ready to {@link #build() create new source}
* @since 1.0
*/
public SourceBuilder mimeType(@SuppressWarnings("hiding") String mimeType) {
validateMimeType(mimeType);
this.mimeType = mimeType;
return this;
}
/**
* Enables or disables code caching for this source. By default code caching is enabled. If
* true
then the source does not require parsing every time this source is
* evaluated. If false
then the source requires parsing every time the source
* is evaluated but does not remember any code. Disabling caching may be useful if the
* source is known to only be evaluated once.
*
* If a source instance is no longer referenced by the client then all code caches will be
* freed automatically. Also, if the underlying context or engine is no longer referenced
* then cached code for evaluated sources will be freed automatically.
*
* @return instance of this
builder ready to {@link #build() create new source}
* @since 1.0
*/
public SourceBuilder cached(boolean enabled) {
this.cached = enabled;
return this;
}
/**
* Marks the source as internal. Internal sources are those that aren't created by user, but
* rather inherently present by the language system. Calling this method influences result
* of create {@link Source#isInternal()}
*
* @return the instance of this builder
* @since 1.0
*/
public SourceBuilder internal(boolean enabled) {
this.internal = enabled;
return this;
}
/**
* Marks the source as interactive. Evaluation of interactive sources by an
* {@link com.oracle.truffle.api.TruffleLanguage.Registration#interactive() interactive
* language} can use the {@link com.oracle.truffle.api.TruffleLanguage.Env environment}
* streams to print the result and read an input. However, non-interactive languages are
* expected to ignore the interactive property of sources and not use the environment
* streams. Any desired printing of the evaluated result provided by a non-interactive
* language needs to be handled by the caller. Calling of this method influences the result
* of {@link Source#isInteractive()}.
*
* @return the instance of this builder
* @since 1.0
*/
public SourceBuilder interactive(boolean enabled) {
this.interactive = enabled;
return this;
}
/**
* Assigns new {@link URI} to the {@link #build() to-be-created} {@link Source}. Each source
* provides {@link Source#getURI()} as a persistent identification of its location. A
* default value for the method is deduced from the location or content, but one can change
* it by using this method
*
* @param ownUri the URL to use instead of default one, cannot be null
* @return the instance of this builder
* @since 1.0
*/
public SourceBuilder uri(URI ownUri) {
this.uri = ownUri;
return this;
}
/**
* Uses configuration of this builder to create new {@link Source} object. The method throws
* an {@link IOException} if an error loading the source occured.
*
* @return the source object
* @since 1.0
*/
public Source build() throws IOException {
assert this.language != null;
Source source = buildSource(this.language, this.origin, this.name, this.mimeType, this.content, this.uri, this.internal, this.interactive, this.cached, false);
// make sure origin is not consumed again if builder is used twice
if (source.hasBytes()) {
this.content = source.getBytes();
} else {
assert source.hasCharacters();
this.content = source.getCharacters();
}
assert source.getName() != null;
assert !source.hasCharacters() || source.getCharacters() != null;
assert !source.hasBytes() || source.getBytes() != null;
assert source.getLanguage() != null;
return source;
}
}
/**
* Allows one to specify additional attribute before {@link #build() creating} new
* {@link Source} instance.
*
* @see SourceBuilder For examples on how to use it.
* @since 1.0
*/
public final class LiteralBuilder extends SourceBuilder {
LiteralBuilder(String language, Object origin) {
super(language, origin);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder name(String newName) {
return (LiteralBuilder) super.name(newName);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder mimeType(String newMimeType) {
return (LiteralBuilder) super.mimeType(newMimeType);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder cached(boolean cached) {
return (LiteralBuilder) super.cached(cached);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder internal(boolean enabled) {
return (LiteralBuilder) super.internal(enabled);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder interactive(boolean enabled) {
return (LiteralBuilder) super.interactive(enabled);
}
/**
* {@inheritDoc}
*
* @since 1.0
*/
@Override
public LiteralBuilder uri(URI ownUri) {
return (LiteralBuilder) super.uri(ownUri);
}
/**
* Uses configuration of this builder to create new {@link Source} object.
*
* @return the source object
* @since 1.0
*/
@Override
public Source build() {
try {
return super.build();
} catch (IOException e) {
throw silenceException(RuntimeException.class, e);
}
}
}
/**
* @since 0.15
* @deprecated use {@link SourceBuilder} instead.
*/
@Deprecated
public class Builder {
private final Object origin;
private URI uri;
private String name;
private String mime;
private String language;
private CharSequence characters;
private boolean internal;
private boolean interactive;
private boolean cached = true;
Builder(Object origin) {
this.origin = origin;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#name(String)}
*/
@Deprecated
@SuppressWarnings("unchecked")
public Builder name(String newName) {
Objects.requireNonNull(newName);
this.name = newName;
return (Builder) this;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#mimeType(String)}
*/
@Deprecated
@SuppressWarnings("unchecked")
public Builder mimeType(String newMimeType) {
Objects.requireNonNull(newMimeType);
this.mime = newMimeType;
return (Builder) this;
}
/**
* @since 1.0
* @deprecated see {@link SourceBuilder#cached(boolean)}
*/
@Deprecated
public Builder cached(@SuppressWarnings("hiding") boolean cached) {
this.cached = cached;
return this;
}
/**
* @since 0.28
* @deprecated without replacement. The language is now provided when a
* {@link SourceBuilder} is constructed.
*/
@Deprecated
@SuppressWarnings("unchecked")
public Builder language(String newLanguage) {
Objects.requireNonNull(newLanguage);
if (this.mime == null) {
this.mime = "x-unknown";
}
this.language = newLanguage;
return (Builder) this;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#internal(boolean)}
*/
@Deprecated
public Builder internal() {
this.internal = true;
return this;
}
/**
* @since 0.21
* @deprecated see {@link SourceBuilder#interactive(boolean)}
*/
@Deprecated
public Builder interactive() {
this.interactive = true;
return this;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#content(CharSequence)}
*/
@Deprecated
public Builder uri(URI ownUri) {
Objects.requireNonNull(ownUri);
this.uri = ownUri;
return this;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#content(CharSequence)}
*/
@SuppressWarnings("unchecked")
@Deprecated
public Builder content(String code) {
this.characters = code;
return (Builder) this;
}
/**
* @since 0.28
* @deprecated see {@link SourceBuilder#content(CharSequence)}
*/
@SuppressWarnings("unchecked")
@Deprecated
public Builder content(CharSequence code) {
this.characters = code;
return (Builder) this;
}
/**
* @since 0.15
* @deprecated see {@link SourceBuilder#build()}
*/
@SuppressWarnings("unused")
@Deprecated
public Source build() throws E1, E2, E3 {
try {
Source source = buildSource(this.language, this.origin, this.name, this.mime, this.characters, this.uri, this.internal, this.interactive, this.cached, true);
// legacy sources must have character sources
assert source.hasCharacters();
// make sure origin is not consumed again if builder is used twice
this.characters = source.getCharacters();
if (source.getMimeType() == null) {
throw raise(RuntimeException.class, new MissingMIMETypeException());
}
if (source.getName() == null) {
throw raise(RuntimeException.class, new MissingNameException());
}
return source;
} catch (IOException ex) {
throw raise(RuntimeException.class, ex);
}
}
}
static {
// force loading source accessor
SourceAccessor.allLoaders();
}
}
// @formatter:off
// Checkstyle: stop
class SourceSnippets {
public static Source fromFile(TruffleFile file) throws IOException {
// BEGIN: SourceSnippets#fromFile
assert file.getName().endsWith(".java") :
"Imagine 'c:\\sources\\Example.java' file";
String language = Source.findLanguage(file);
Source source = Source.newBuilder(language, file).build();
assert file.getName().equals(source.getName());
assert file.getPath().equals(source.getPath());
assert file.toUri().equals(source.getURI());
// END: SourceSnippets#fromFile
return source;
}
public static Source likeFileName(TruffleFile file) throws IOException {
// BEGIN: SourceSnippets#likeFileName
String language = Source.findLanguage(file);
Source source = Source.newBuilder(language, file.getCanonicalFile()).
name(file.getPath()).
build();
// END: SourceSnippets#likeFileName
return source;
}
public static Source fromURL(Class> relativeClass) throws IOException, URISyntaxException {
// BEGIN: SourceSnippets#fromURL
URL resource = relativeClass.getResource("sample.js");
Source source = Source.newBuilder("js", resource)
.build();
assert resource.toExternalForm().equals(source.getPath());
assert "sample.js".equals(source.getName());
assert resource.toURI().equals(source.getURI());
// END: SourceSnippets#fromURL
return source;
}
public static Source fromURLWithOwnContent(Class> relativeClass) {
// BEGIN: SourceSnippets#fromURLWithOwnContent
URL resource = relativeClass.getResource("sample.js");
Source source = Source.newBuilder("js", resource)
.content("{}")
.build();
assert resource.toExternalForm().equals(source.getPath());
assert "sample.js".equals(source.getName());
assert resource.toExternalForm().equals(source.getURI().toString());
assert "{}".equals(source.getCharacters());
// END: SourceSnippets#fromURLWithOwnContent
return source;
}
public static Source fromReader(Class> relativeClass) throws IOException {
// BEGIN: SourceSnippets#fromReader
Reader stream = new InputStreamReader(
relativeClass.getResourceAsStream("sample.js")
);
Source source = Source.newBuilder("js", stream, "sample.js")
.build();
assert "sample.js".equals(source.getName());
// END: SourceSnippets#fromReader
return source;
}
public static Source fromAString() {
// BEGIN: SourceSnippets#fromAString
Source source = Source.newBuilder("js", "function() {\n"
+ " return 'Hi';\n"
+ "}\n", "hi.js").build();
assert "hi.js".equals(source.getName());
// END: SourceSnippets#fromAString
return source;
}
public static Source fromBytes() {
// BEGIN: SourceSnippets#fromBytes
byte[] bytes = new byte[] {/* Binary */};
Source source = Source.newBuilder("llvm",
ByteSequence.create(bytes),
"").build();
// END: SourceSnippets#fromBytes
return source;
}
public static boolean loaded = true;
}
// @formatter:on