All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graalvm.polyglot.Source Maven / Gradle / Ivy

/*
 * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package org.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.util.Objects;

import org.graalvm.polyglot.impl.AbstractPolyglotImpl.AbstractSourceImpl;

/**
 * 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 lazily 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. * *

Immutability of {@link Source}

* *

* {@link Source} is an immutable object - once (lazily) loaded, it remains the same. The source * object can be associated with various attributes like {@link #getName()} and {@link #getURI()} * and these are immutable as well. 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 by creating clones of the source via * {@link Builder#name(java.lang.String)}, {@link Builder#uri(java.net.URI)} methods. *

*

* While {@link Source} is immutable, the world around it is changing. The content of a file from * which a {@link Source#newBuilder(String, java.io.File) source has been read} may change few * seconds later. How can we balance the immutability with ability to see real state of the world? * In this case, one can load of a new version of the {@link Source#newBuilder(String, File) source * for the same file}. The newly loaded {@link Source} will be different than the previous one, * however it will have the same attributes ({@link #getName()}. There isn't much to do about this - * just keep in mind that there can be multiple different {@link Source} objects representing the * same {@link #getURI() source origin}. *

* * @since 1.0 */ public final class Source { private static volatile AbstractSourceImpl IMPL; static AbstractSourceImpl getImpl() { if (IMPL == null) { synchronized (Engine.class) { if (IMPL == null) { IMPL = Engine.getImpl().getSourceImpl(); SourceSection.IMPL = Engine.getImpl().getSourceSectionImpl(); } } } return IMPL; } // non final to support legacy code final String language; final Object impl; Source(String language, Object impl) { this.language = language; this.impl = impl; } /** * Returns the language this source created with. * * @since 1.0 */ public String getLanguage() { return language; } /** * 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 1.0 */ public String getName() { return getImpl().getName(impl); } /** * 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 1.0 */ public String getPath() { return getImpl().getPath(impl); } /** * 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 1.0 */ public boolean isInteractive() { return getImpl().isInteractive(impl); } /** * The URL if the source is retrieved via URL. * * @return URL or null * @since 1.0 */ public URL getURL() { return getImpl().getURL(impl); } /** * 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 1.0 */ public URI getURI() { return getImpl().getURI(impl); } /** * Access to the source contents. * * @since 1.0 */ public Reader getReader() { return getImpl().getReader(impl); } /** * Access to the source contents. Causes the contents of this source to be loaded if they are * loaded lazily. * * @since 1.0 */ public InputStream getInputStream() { return getImpl().getInputStream(impl); } /** * Gets the number of characters in the source. Causes the contents of this source to be loaded * if they are loaded lazily. * * @since 1.0 */ public int getLength() { return getImpl().getLength(impl); } /** * Returns the complete text of the code. Causes the contents of this source to be loaded if * they are loaded lazily. * * @since 1.0 */ public CharSequence getCharacters() { return getImpl().getCode(impl); } /** * @since 1.0 * @deprecated use {@link #getCharacters()} */ @Deprecated public CharSequence getCode() { return getImpl().getCode(impl); } /** * 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. * * @since 1.0 */ public CharSequence getCharacters(int lineNumber) { return getImpl().getCode(impl, lineNumber); } /** * @since 1.0 * @deprecated use {@link #getCharacters(int)} */ @Deprecated public CharSequence getCode(int lineNumber) { return getImpl().getCode(impl, lineNumber); } /** * 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. * * @since 1.0 */ public int getLineCount() { return getImpl().getLineCount(impl); } /** * 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 IllegalArgumentException if the offset is outside the text contents * @since 1.0 */ public int getLineNumber(int offset) throws IllegalArgumentException { return getImpl().getLineNumber(impl, 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 IllegalArgumentException if the offset is outside the text contents * @since 1.0 */ public int getColumnNumber(int offset) throws IllegalArgumentException { return getImpl().getColumnNumber(impl, offset); } /** * Given a 1-based line number, return the 0-based offset of the first character in the line. * * @throws IllegalArgumentException if there is no such line in the text * @since 1.0 */ public int getLineStartOffset(int lineNumber) throws IllegalArgumentException { return getImpl().getLineStartOffset(impl, 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 IllegalArgumentException if there is no such line in the text * @since 1.0 */ public int getLineLength(int lineNumber) throws IllegalArgumentException { return getImpl().getLineLength(impl, lineNumber); } /** * 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 1.0 */ public boolean isInternal() { return getImpl().isInternal(impl); } /** * {@inheritDoc} * * @since 1.0 */ @Override public String toString() { return getImpl().toString(impl); } /** * {@inheritDoc} * * @since 1.0 */ @Override public int hashCode() { return getImpl().hashCode(impl); } /** * {@inheritDoc} * * @since 1.0 */ @Override public boolean equals(Object obj) { Object otherImpl; if (obj instanceof Source) { otherImpl = ((Source) obj).impl; } else { return false; } return getImpl().equals(impl, otherImpl); } /** * Creates a new character source builder. The given characters must not mutate after they were * accessed for the first time. * * @since 1.0 */ public static Builder newBuilder(String language, CharSequence characters, String name) { return EMPTY.new Builder(language, characters).name(name); } /** * * * @since 1.0 */ public static Builder newBuilder(String language, File source) { return EMPTY.new Builder(language, source); } /** * * * @since 1.0 */ public static Builder newBuilder(String language, URL source) { return EMPTY.new Builder(language, source); } /** * * * @since 1.0 */ public static Builder newBuilder(String language, Reader source, String name) { return EMPTY.new Builder(language, source).name(name); } /** * * * @since 1.0 */ public static Source create(String language, CharSequence source) { return newBuilder(language, source, "Unnamed").buildLiteral(); } /** * Finds a language for a given {@link File file} instance. Typically the language is identified * using the file extension and/or using it contents. Returns null if the language * of the given file could not be detected. * * @throws IOException if an error opening the file occurred. * @since 1.0 */ public static String findLanguage(File file) throws IOException { return getImpl().findLanguage(file); } /** * Finds an installed language using a given mime-type. Returns null if no language * was found for a given mime-type. * * @since 1.0 */ public static String findLanguage(String mimeType) { return getImpl().findLanguage(mimeType); } private static final Source EMPTY = new Source(null, null); /** * * * @since 1.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 CharSequence content; Builder(String language, Object origin) { Objects.requireNonNull(language); Objects.requireNonNull(origin); this.language = language; this.origin = origin; } /** * * * @since 1.0 */ public Builder name(String newName) { Objects.requireNonNull(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 1.0 */ public Builder content(String code) { return content((CharSequence) code); } /** * 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. The given characters must not mutate after they were accessed for the * first time. Example: * * {@link SourceSnippets#fromURLWithOwnContent} * * @param code the code to be available via {@link Source#getCharacters()} * @return instance of this builder * @since 1.0 */ public Builder content(CharSequence code) { Objects.requireNonNull(code); this.content = code; 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 1.0 */ public Builder interactive(@SuppressWarnings("hiding") boolean interactive) { this.interactive = interactive; 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 Builder internal(@SuppressWarnings("hiding") boolean internal) { this.internal = internal; 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 1.0 */ public Builder uri(URI newUri) { Objects.requireNonNull(newUri); this.uri = newUri; 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 { return getImpl().build(language, origin, uri, name, content, interactive, internal); } /** * 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 1.0 */ public Source buildLiteral() { if (!(origin instanceof CharSequence)) { throw new UnsupportedOperationException("This method is only supported for string literal. Use build() instead."); } 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) throws IOException { // 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", "").buildLiteral(); // END: SourceSnippets#fromAString return source; } public static boolean loaded = true; } //@formatter:on





© 2015 - 2024 Weber Informatics LLC | Privacy Policy