org.pkl.thirdparty.graalvm.polyglot.Source Maven / Gradle / Ivy
Show all versions of pkl-tools Show documentation
/*
* Copyright (c) 2017, 2022, 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 org.pkl.thirdparty.graalvm.polyglot;
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.nio.charset.Charset;
import java.util.Objects;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl;
import org.pkl.thirdparty.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractSourceDispatch;
import org.pkl.thirdparty.graalvm.polyglot.io.ByteSequence;
/**
* Representation of a source code unit and its contents that can be evaluated in an execution
* {@link Context context}. 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, java.io.File)} 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 Builder#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, java.lang.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 Builder#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.
*
* {@link SourceSnippets#fromBytes}
*
* Attributes
*
* The source object can be associated with various attributes like {@link #getName()} and
* {@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 way to alter values of these attributes.
*
* Character and byte based Sources
*
* A source is {@link #hasBytes() byte} or {@link #hasCharacters() character} based, or none of
* those when no content is specified. 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 Language#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 Builder#mimeType(String) MIME type} or
* {@link Builder#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.
*
* @see Context#eval(Source) To evaluate sources.
* @see Source#findLanguage(File) To detect a language using a File
* @see Source#findLanguage(URL) To detect a language using an URL.
* @see Source#findMimeType(File) To detect a MIME type using a File.
* @see Source#findMimeType(URL) To detect a MIME type using an URL.
* @since 19.0
*/
public final class Source {
private static volatile AbstractPolyglotImpl IMPL;
static AbstractPolyglotImpl getImpl() {
if (IMPL == null) {
synchronized (Engine.class) {
if (IMPL == null) {
IMPL = Engine.getImpl();
}
}
}
return IMPL;
}
final AbstractSourceDispatch dispatch;
final Object receiver;
Source(AbstractSourceDispatch dispatch, Object receiver) {
this.dispatch = dispatch;
this.receiver = receiver;
}
/**
* Returns the language this source created with. The string returned matches the
* {@link Language#getId() id} of the language.
*
* @since 19.0
*/
public String getLanguage() {
return dispatch.getLanguage(receiver);
}
/**
* 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
* @see Builder#name(String)
* @since 19.0
*/
public String getName() {
return dispatch.getName(receiver);
}
/**
* The fully qualified name of the source. In case this source originates from a {@link File},
* then the path is the normalized, {@link File#getCanonicalPath() canonical path} for absolute
* files, or the relative path otherwise. If the source originates from an {@link URL}, then
* it's the path component of the URL.
*
* @since 19.0
*/
public String getPath() {
return dispatch.getPath(receiver);
}
/**
* The URL if the source is retrieved via URL.
*
* @return URL or null
* @since 19.0
*/
public URL getURL() {
return dispatch.getURL(receiver);
}
/**
* Get the URI of the source. Every source has an associated {@link URI}, which can be used as a
* persistent identification of the source. 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, java.io.File) file on a disk} changes and is re-loaded.
*
* @return a URI, never null
* @since 19.0
*/
public URI getURI() {
return dispatch.getURI(receiver);
}
/**
* 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.
*
* One can specify whether a source is interactive when {@link Builder#interactive(boolean)
* building it}.
*
* @return whether this source is marked as interactive
* @since 19.0
*/
public boolean isInteractive() {
return dispatch.isInteractive(receiver);
}
/**
* Gets 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 Builder#internal(boolean) building
* it}.
*
* @return whether this source is marked as internal
* @since 19.0
*/
public boolean isInternal() {
return dispatch.isInternal(receiver);
}
/**
* Returns a new reader that reads from the {@link #getCharacters() characters} provided by this
* source.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 19.0
*/
public Reader getReader() {
return dispatch.getReader(receiver);
}
/**
* @since 19.0
* @deprecated use {@link #getReader()}, {@link #getCharacters()} or {@link #getBytes()}
* instead. The implementation is inefficient and can not distinguish byte and
* character based sources.
*/
@Deprecated(since = "19.0")
public InputStream getInputStream() {
return dispatch.getInputStream(receiver);
}
/**
* Gets the number of characters or bytes of the source.
*
* @since 19.0
*/
public int getLength() {
return dispatch.getLength(receiver);
}
/**
* Returns all characters of the source.
*
* @throws UnsupportedOperationException if this source cannot contain {@link #hasCharacters()
* characters}.
* @since 19.0
*/
public CharSequence getCharacters() {
return dispatch.getCharacters(receiver);
}
/**
* 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(File) files} or
* {@link #findMimeType(URL) urls}
*
* @see Language#getDefaultMimeType()
* @see Language#getMimeTypes()
* @see Source#findMimeType(File)
* @see Source#findMimeType(URL)
* @return MIME type of this source or null
, if not explicitly set.
* @since 19.0
*/
public String getMimeType() {
return dispatch.getMimeType(receiver);
}
/**
* 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}.
* @see #hasCharacters()
* @since 19.0
*/
public CharSequence getCharacters(int lineNumber) {
return dispatch.getCharacters(receiver, lineNumber);
}
/**
* 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 19.0
*/
public ByteSequence getBytes() {
return dispatch.getBytes(receiver);
}
/**
* Returns true
if this source represents a character based source, else
* false
. A source is either a byte based, a character based, or with no content,
* but never both byte and character based 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 19.0
*/
public boolean hasCharacters() {
return dispatch.hasCharacters(receiver);
}
/**
* Returns true
if this source represents a byte based source, else
* false
. A source is either a byte based, a character based, or with no content,
* but never both byte and character based at the same time.
*
* The method {@link #getBytes()} is only supported if this method returns true
.
*
* @see #getBytes()
* @since 19.0
*/
public boolean hasBytes() {
return dispatch.hasBytes(receiver);
}
/**
* The number of text lines of a character based 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 19.0
*/
public int getLineCount() {
return dispatch.getLineCount(receiver);
}
/**
* 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 19.0
*/
public int getLineNumber(int offset) throws IllegalArgumentException {
return dispatch.getLineNumber(receiver, 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 19.0
*/
public int getColumnNumber(int offset) throws IllegalArgumentException {
return dispatch.getColumnNumber(receiver, 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 19.0
*/
public int getLineStartOffset(int lineNumber) throws IllegalArgumentException {
return dispatch.getLineStartOffset(receiver, 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 19.0
*/
public int getLineLength(int lineNumber) throws IllegalArgumentException {
return dispatch.getLineLength(receiver, lineNumber);
}
/**
* {@inheritDoc}
*
* @since 19.0
*/
@Override
public String toString() {
return dispatch.toString(receiver);
}
/**
* {@inheritDoc}
*
* @since 19.0
*/
@Override
public int hashCode() {
return dispatch.hashCode(receiver);
}
/**
* {@inheritDoc}
*
* @since 19.0
*/
@Override
public boolean equals(Object obj) {
Object otherImpl;
if (obj instanceof Source) {
otherImpl = ((Source) obj).receiver;
} else {
return false;
}
return dispatch.equals(receiver, otherImpl);
}
/**
* Creates a new character based literal source from a character sequence. The given characters
* must not mutate after they were accessed for the first time.
*
* Use this method for sources that do originate from a literal. For file or URL sources use the
* appropriate builder constructor and {@link Builder#content(CharSequence)}.
*
* 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 19.0
*/
public static Builder newBuilder(String language, CharSequence characters, String name) {
return EMPTY.new Builder(language, characters).name(name);
}
/**
* Creates a new byte based literal source from a byte sequence. The given bytes must not mutate
* after they were accessed for the first time.
*
* Use this method for sources that do originate from a literal. For file or URL sources use the
* appropriate builder constructor and {@link Builder#content(CharSequence)}.
*
* 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 19.0
*/
public static Builder newBuilder(String language, ByteSequence bytes, String name) {
return EMPTY.new Builder(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 Language#getDefaultMimeType() default MIME type} of the
* language, the {@link Builder#content(ByteSequence) contents} or the specified
* {@link Builder#mimeType(String) MIME type}. A language may be detected from an existing file
* using {@link #findLanguage(File)}.
*
* Example usage: {@link SourceSnippets#fromFile}
*
* @param language the language id, must not be null
* @param file the file to use, must not be null
* @since 19.0
*/
public static Builder newBuilder(String language, File file) {
return EMPTY.new Builder(language, file);
}
/**
* 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 Language#getDefaultMimeType() default MIME type} of the
* language, the {@link Builder#content(ByteSequence) contents} or the specified
* {@link Builder#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 19.0
*/
public static Builder newBuilder(String language, URL url) {
return EMPTY.new Builder(language, url);
}
/**
* Creates new character based literal source from a reader.
*
* Use this method for sources that do originate from a literal. For file or URL sources use the
* appropriate builder constructor and {@link Builder#content(CharSequence)}.
*
* Example usage: {@link SourceSnippets#fromReader}
*
* @since 19.0
*/
public static Builder newBuilder(String language, Reader source, String name) {
return EMPTY.new Builder(language, source).name(name);
}
/**
* Shortcut for creating a source object from a language and char sequence. The given characters
* must not mutate after they were accessed for the first time.
*
* Use for sources that do not come from a file, or URL. If they do, use the appropriate builder
* and {@link Builder#content(CharSequence)}.
*
* @since 19.0
*/
public static Source create(String language, CharSequence source) {
return newBuilder(language, source, "Unnamed").buildLiteral();
}
/**
* Returns the id of 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 19.0
*/
public static String findLanguage(File file) throws IOException {
return getImpl().findLanguage(file);
}
/**
* Returns the id of 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 19.0
*/
public static String findLanguage(URL url) throws IOException {
return getImpl().findLanguage(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 File} may require to opening the file.
*
* @throws IOException if an error opening the file occurred.
* @see #findLanguage(File)
* @since 19.0
*/
public static String findMimeType(File file) throws IOException {
return getImpl().findMimeType(file);
}
/**
* 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 19.0
*/
public static String findMimeType(URL url) throws IOException {
return getImpl().findMimeType(url);
}
/**
* 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()}.
* Mime-types don't adhere to the MIME type format will return null
.
*
* @since 19.0
*/
public static String findLanguage(String mimeType) {
return getImpl().findLanguage(mimeType);
}
@SuppressWarnings({"unchecked", "unused"})
static RuntimeException silenceException(Class type, Exception ex) throws E {
throw (E) ex;
}
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(mimeType);
}
if (mimeType.indexOf('/', index + 1) != -1) {
throw invalidMimeType(mimeType);
}
}
private static IllegalArgumentException invalidMimeType(String mimeType) {
return new IllegalArgumentException(String.format("Invalid MIME type '%s' provided. A MIME type consists of a type and a subtype separated by '/'.", mimeType));
}
private static final Source EMPTY = new Source(null, null);
/**
* Represents a builder to build {@link Source} objects.
*
* 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 19.0
*/
public class Builder {
private final String language;
private final Object origin;
private URI uri;
private String name;
private boolean interactive;
private boolean internal;
private boolean cached = true;
private Object content;
private String mimeType;
private Charset fileEncoding;
Builder(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 19.0
*/
public Builder name(String newName) {
this.name = newName;
return this;
}
/**
* Specifies 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. Example:
*
* {@link SourceSnippets#fromURLWithOwnContent}
*
* @param code the code to be available via {@link Source#getCharacters()}
* @return instance of this builder
* @since 19.0
*/
public Builder content(String code) {
return content((CharSequence) code);
}
/**
* 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 19.0
*/
public Builder content(CharSequence characters) {
Objects.requireNonNull(characters);
this.content = characters;
return 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 19.0
*/
public Builder content(ByteSequence bytes) {
Objects.requireNonNull(bytes);
this.content = bytes;
return 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 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(File) files} or
* {@link #findMimeType(URL) urls}. If a source is {@link Source#hasBytes() binary} based
* then the MIME type must also be a binary based MIME type. All MIME types starting with
* 'text/' will be interpreted as character based MIME types.
*
* @see Language#getDefaultMimeType()
* @see Language#getMimeTypes()
* @see Source#findMimeType(File)
* @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 19.0
*/
public Builder mimeType(@SuppressWarnings("hiding") String mimeType) {
validateMimeType(mimeType);
this.mimeType = mimeType;
return this;
}
/**
* Marks the source as interactive. {@link Context#eval Evaluation} of interactive sources
* by an {@link Language#isInteractive() interactive language} can use the {@link Context}
* output 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 polyglot
* engine 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 19.0
*/
public Builder interactive(@SuppressWarnings("hiding") boolean interactive) {
this.interactive = interactive;
return this;
}
/**
* Set 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.
*
*
* @return the instance of this builder
* @since 19.0
*/
public Builder internal(@SuppressWarnings("hiding") boolean internal) {
this.internal = internal;
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
* {@link Context#eval(Source) 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.
*
* 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 19.0
*/
public Builder cached(@SuppressWarnings("hiding") boolean cached) {
this.cached = cached;
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 newUri the URL to use instead of default one, cannot be null
* @return the instance of this builder
* @since 19.0
*/
public Builder uri(URI newUri) {
Objects.requireNonNull(newUri);
this.uri = newUri;
return this;
}
/**
* Assigns an encoding used to read the file content. If the encoding is {@code null} then
* the file contained encoding information is used. If the file doesn't provide an encoding
* information the default {@code UTF-8} encoding is used.
*
* @param encoding the new file encoding to be used for reading the content
* @return instance of this
builder ready to {@link #build() create new source}
* @since 19.0
*/
public Builder encoding(Charset encoding) {
this.fileEncoding = encoding;
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 occurred.
*
* @return the source object
* @since 19.0
*/
public Source build() throws IOException {
Source source = getImpl().build(language, origin, uri, name, mimeType, content, interactive, internal, cached, fileEncoding, null, null);
// 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();
}
return source;
}
/**
* Uses configuration of this builder to create new {@link Source} object. This method can
* only be used if the builder was created as
* {@link Source#newBuilder(String, CharSequence, String) string literal} builder and
* otherwise throws an {@link UnsupportedOperationException}.
*
* @return the source object
* @since 19.0
*/
public Source buildLiteral() {
try {
return build();
} catch (IOException e) {
throw new AssertionError("No error expected.", e);
}
}
}
}
//@formatter:off
//Checkstyle: stop
class SourceSnippets {
public static Source fromFile(File dir, String name) throws IOException {
// BEGIN: SourceSnippets#fromFile
File file = new File(dir, name);
assert name.endsWith(".java") : "Imagine proper 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(String fileName) throws IOException {
// BEGIN: SourceSnippets#likeFileName
File file = new File(fileName);
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 URISyntaxException, IOException {
// 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("{}")
.buildLiteral();
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").buildLiteral();
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),
"").buildLiteral();
// END: SourceSnippets#fromBytes
return source;
}
public static boolean loaded = true;
}
//@formatter:on