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

com.oracle.truffle.api.source.Source Maven / Gradle / Ivy

Go to download

Truffle is a multi-language framework for executing dynamic languages that achieves high performance when combined with Graal.

There is a newer version: 1.0.0-rc7
Show newest version
/*
 * Copyright (c) 2013, 2016, 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 com.oracle.truffle.api.source;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.spi.FileTypeDetector;

import com.oracle.truffle.api.CompilerAsserts;
import com.oracle.truffle.api.TruffleLanguage.Registration;
import com.oracle.truffle.api.nodes.Node;
import java.io.InputStreamReader;
import java.util.Objects;
import java.net.URI;
import java.net.URISyntaxException;

/**
 * Representation of a source code unit and its contents. Source instances are created by using one
 * of existing factory methods, each loading the file from a different source/medium.
 *
 * 

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} * * Methods of interest are {@link Source#fromFileName(String)}, * {@link Source#fromFileName(String, boolean)}, and {@link Source#find(String)}. * *

Read from an URL

* * One can read remote or in JAR resources using the {@link Source#fromURL(URL, String)} factory: *
* * {@link SourceSnippets#fromURL} * * Each URL source is represented as a canonical object, indexed by the URL. Contents are * read eagerly and cached locally. * *

Source from a literal text

* * An anonymous immutable code snippet can be created from a string via the * {@link Source#fromText(CharSequence, String)} factory method:
* * {@link SourceSnippets#fromAString} * * the created {@link Source} doesn't have associated {@link #getMimeType() mime type}. One has to * additionally attach one to it via {@link #withMimeType(java.lang.String)} method. * *

Reading from a stream

* * If one has a {@link Reader} one can convert its content into a {@link Source} via * {@link Source#fromReader(Reader, String)} method:
* * {@link SourceSnippets#fromReader} * * the content is read eagerly and it doesn't have associated {@link #getMimeType() mime * type}. One has to additionally attach one to it via {@link #withMimeType(java.lang.String)} * method. * * * *

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()}, {@link #getShortName()} * , {@link #getPath()}, {@link #getMimeType()} 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 #withMimeType(java.lang.String)}, {@link #withName(java.lang.String)}, * {@link #withPath(java.lang.String)} or {@link #withShortName(java.lang.String)} methods. *

*

* While {@link Source} is immutable, the world around it is changing. The content of a file from * which a {@link Source#fromFileName(java.lang.String) 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 request a reload of a new version of the * {@link Source#fromFileName(java.lang.String, boolean) 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()}, presumably also {@link #getMimeType()}, etc.). There isn't much to do about * this - just keep in mind that there can be multiple different {@link Source} objects representing * the same source origin. *

* * @since 0.8 or earlier */ public abstract class Source { // TODO (mlvdv) consider canonicalizing and reusing SourceSection instances // TODO (mlvdv) connect SourceSections into a spatial tree for fast geometric lookup static boolean fileCacheEnabled = true; private static final String NO_FASTPATH_SUBSOURCE_CREATION_MESSAGE = "do not create sub sources from compiled code"; private final Content content; private String name; private String shortName; private String path; private String mimeType; private TextMap textMap; /** * Locates an existing instance of a {@link Source} with given {@link #getName() name}. * * @param name the {@link #getName() name} of a source to seek for * @return found source or null if no source with name is known * @since 0.8 or earlier */ public static Source find(String name) { return SourceImpl.findSource(name); } /** * Gets the canonical representation of a source file, whose contents will be read lazily and * then cached. The {@link #getShortName() short name} of the source is equal to * {@link File#getName() name of the file}. The {@link #getName() name} of the file is exactly * the provided fileName string. The {@link #getPath() path} is * {@link File#getCanonicalPath() canonical path} of the provided file name. * * @param fileName path to the file with the source * @param reload forces any existing {@link Source} cache to be cleared, forcing a re-read - as * a result the newly returned {@link Source} may have content different to any * previously loaded one, which is up-to-date with current state on the disk * @return source representing the file's content * @throws IOException if the file cannot be read * @since 0.8 or earlier */ public static Source fromFileName(String fileName, boolean reload) throws IOException { if (!reload) { Source source = find(fileName); if (source != null && source.content() instanceof FileSourceImpl) { return source; } } final File file = new File(fileName); if (!file.canRead()) { throw new IOException("Can't read file " + fileName); } final String path = file.getCanonicalPath(); final FileSourceImpl content = new FileSourceImpl(file, fileName, path); return new SourceImpl(content); } /** * Gets the canonical representation of a source file, whose contents will be read lazily and * then cached. The {@link #getShortName() short name} of the source is equal to * {@link File#getName() name of the file}. The {@link #getName() name} of the file is exactly * the provided fileName string. The {@link #getPath() path} is * {@link File#getCanonicalPath() canonical path} of the provided file name. * * @param fileName path to the file with the source * @return source representing the file's content * @throws IOException if the file cannot be read * @since 0.8 or earlier */ public static Source fromFileName(String fileName) throws IOException { return fromFileName(fileName, false); } /** * Gets the canonical representation of a source file whose contents are the responsibility of * the client: *
    *
  • If no Source exists corresponding to the provided file name, then a new Source is created * whose contents are those provided. It is confirmed that the file resolves to a file name, so * it can be indexed by canonical path. However there is no confirmation that the text supplied * agrees with the file's contents or even whether the file is readable.
  • *
  • If a Source exists corresponding to the provided file name, and that Source was created * originally by this method, then that Source will be returned after replacement of its * contents with no further confirmation.
  • *
  • If a Source exists corresponding to the provided file name, and that Source was not * created originally by this method, then an exception will be raised.
  • *
* * @param chars textual source code already read from the file, must not be null * @param fileName * @return canonical representation of the file's contents. * @throws IOException if the file cannot be found, or if an existing Source not created by this * method matches the file name * @since 0.8 or earlier */ public static Source fromFileName(CharSequence chars, String fileName) throws IOException { CompilerAsserts.neverPartOfCompilation("do not call Source.fromFileName from compiled code"); assert chars != null; final File file = new File(fileName); // We are going to trust that the fileName is readable. final String path = file.getCanonicalPath(); Content content = new ClientManagedFileSourceImpl(file, fileName, path, chars); Source source = new SourceImpl(content); return source; } /** * Creates an anonymous source from literal text. The {@link #getName() name} of the source is * name. The {@link #getShortName()} is also name, as well as * {@link #getPath() path}. * * @param chars textual source code * @param name a note about the origin, for error messages and debugging - used as * {@link Source#getName()} and {@link Source#getShortName()}, the name can be * null * @return a newly created, source representation * @since 0.8 or earlier */ public static Source fromText(CharSequence chars, String name) { CompilerAsserts.neverPartOfCompilation("do not call Source.fromText from compiled code"); Content content = new LiteralSourceImpl(name, chars.toString()); return new SourceImpl(content); } /** * Creates an anonymous source from literal text that is provided incrementally after creation. * The {@link #getName() name}, {@link #getShortName() short name} and {@link #getPath() path} * are set to name. * * @param name name for the newly created source * @return a newly created, non-indexed, initially empty, appendable source representation * @since 0.8 or earlier */ public static Source fromAppendableText(String name) { CompilerAsserts.neverPartOfCompilation("do not call Source.fromAppendableText from compiled code"); Content content = new AppendableLiteralSourceImpl(name); return new SourceImpl(content); } /** * Creates a source from literal text that can be retrieved by name, with no assumptions about * the structure or meaning of the name. If the name is already in the index, the new instance * will replace the previously existing instance in the index. * * @param chars textual source code * @param name string to use for indexing/lookup * @return a newly created, source representation * @since 0.8 or earlier * @deprecated use {@link #fromText(java.lang.CharSequence, java.lang.String)} */ @Deprecated public static Source fromNamedText(CharSequence chars, String name) { CompilerAsserts.neverPartOfCompilation("do not call Source.fromNamedText from compiled code"); Content content = new LiteralSourceImpl(name, chars.toString()); final Source source = new SourceImpl(content); return source; } /** * Creates a source from literal text that is provided incrementally after creation and which * can be retrieved by name, with no assumptions about the structure or meaning of the name. If * the name is already in the index, the new instance will replace the previously existing * instance in the index. * * @param name string to use for indexing/lookup * @return a newly created, indexed, initially empty, appendable source representation * @since 0.8 or earlier * @deprecated use {@link #fromAppendableText(java.lang.String)} */ @Deprecated public static Source fromNamedAppendableText(String name) { CompilerAsserts.neverPartOfCompilation("do not call Source.fromNamedAppendable from compiled code"); final Content content = new AppendableLiteralSourceImpl(name); final Source source = new SourceImpl(content); return source; } /** * Creates a {@linkplain Source Source instance} that represents the contents of a sub-range of * an existing {@link Source}. * * @param base an existing Source instance * @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 * @since 0.8 or earlier */ public static Source subSource(Source base, int baseCharIndex, int length) { CompilerAsserts.neverPartOfCompilation(NO_FASTPATH_SUBSOURCE_CREATION_MESSAGE); final SubSourceImpl subSource = SubSourceImpl.create(base, baseCharIndex, length); return new SourceImpl(subSource); } /** * Creates a {@linkplain Source Source instance} that represents the contents of a sub-range at * the end of an existing {@link Source}. * * @param base an existing Source instance * @param baseCharIndex 0-based index of the first character of the sub-range * @return a new instance representing a sub-range at the end of another Source * @throws IllegalArgumentException if the index is out of range * @since 0.8 or earlier */ public static Source subSource(Source base, int baseCharIndex) { CompilerAsserts.neverPartOfCompilation(NO_FASTPATH_SUBSOURCE_CREATION_MESSAGE); return subSource(base, baseCharIndex, base.getLength() - baseCharIndex); } /** * Creates a source whose contents will be read immediately from a URL and cached. * * @param url * @param description identifies the origin, possibly useful for debugging * @return a newly created, non-indexed source representation * @throws IOException if reading fails * @since 0.8 or earlier */ public static Source fromURL(URL url, String description) throws IOException { CompilerAsserts.neverPartOfCompilation("do not call Source.fromURL from compiled code"); Content content = URLSourceImpl.get(url, description); return new SourceImpl(content); } /** * Creates a source whose contents will be read immediately and cached. * * @param reader * @param description a note about the origin, possibly useful for debugging * @return a newly created, non-indexed source representation * @throws IOException if reading fails * @since 0.8 or earlier */ public static Source fromReader(Reader reader, String description) throws IOException { CompilerAsserts.neverPartOfCompilation("do not call Source.fromReader from compiled code"); Content content = new LiteralSourceImpl(description, read(reader)); return new SourceImpl(content); } /** * Creates a source from raw bytes. This can be used if your parser returns byte indices instead * of character indices. The returned source is however still indexed by character. * * The {@link #getName() name}, {@link #getShortName() short name} and {@link #getPath() path} * are set to value of name * * @param bytes the raw bytes of the source * @param name name of the created source * @param charset how to decode the bytes into Java strings * @return a newly created, non-indexed source representation * @since 0.8 or earlier */ public static Source fromBytes(byte[] bytes, String name, Charset charset) { return fromBytes(bytes, 0, bytes.length, name, charset); } /** * Creates a source from raw bytes. This can be used if your parser returns byte indices instead * of character indices. The returned source is however still indexed by character. Offsets are * starting at byteIndex. * * The {@link #getName() name}, {@link #getShortName() short name} and {@link #getPath() path} * are set to value of name * * @param bytes the raw bytes of the source * @param byteIndex where the string starts in the byte array * @param length the length of bytes to use from the byte array * @param name name of the created source * @param charset how to decode the bytes into Java strings * @return a newly created, non-indexed source representation * @since 0.8 or earlier */ public static Source fromBytes(byte[] bytes, int byteIndex, int length, String name, Charset charset) { CompilerAsserts.neverPartOfCompilation("do not call Source.fromBytes from compiled code"); Content content = new BytesSourceImpl(name, bytes, byteIndex, length, charset); return new SourceImpl(content); } // TODO (mlvdv) enable per-file choice whether to cache? /** * Enables/disables caching of file contents, disabled by default. Caching of sources * created from literal text or readers is always enabled. * * @since 0.8 or earlier * @deprecated globally configurable caching is uncontrollable as it allows any piece of code to * influence behavior of completely independent piece of code */ @Deprecated public static void setFileCaching(boolean enabled) { fileCacheEnabled = enabled; } static String read(Reader reader) throws IOException { final BufferedReader bufferedReader = new BufferedReader(reader); final StringBuilder builder = new StringBuilder(); final char[] buffer = new char[1024]; try { while (true) { final int n = bufferedReader.read(buffer); if (n == -1) { break; } builder.append(buffer, 0, n); } } finally { bufferedReader.close(); } return builder.toString(); } Source(Content content) { this.content = content; } Content content() { return content; } /** * 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 at least as long as * {@link #getShortName()} and as long, or shorter than {@link #getPath()}. * * One can change name for an existing source by creating its clone via * {@link #withName(java.lang.String)} method. * * @return the name of the guest language program * @since 0.8 or earlier */ public String getName() { return name == null ? content().getName() : name; } /** * Returns a short version of the name of the resource holding a guest language program (as * described in {@link #getName()}). For example, this could be just the name of the file, * rather than a full path. * * One can change name for an existing source by creating its clone via * {@link #withShortName(java.lang.String)} method. * * @return the short name of the guest language program * @since 0.8 or earlier */ public String getShortName() { return shortName == null ? content().getShortName() : shortName; } /** * 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}. * * One can change path associated with a file by calling {@link #withPath(java.lang.String)} and * obtaining cloned instance of the source with new path. * * @since 0.8 or earlier */ public String getPath() { return path == null ? content().getPath() : path; } /** * The URL if the source is retrieved via URL. * * @return URL or null * @since 0.8 or earlier */ public URL getURL() { return content().getURL(); } /** * 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.Debugger#setLineBreakpoint(int, java.net.URI, int, boolean) * register a breakpoint using a URI} to a source that isn't loaded yet and it will be activated * when the source is * {@link com.oracle.truffle.api.vm.PolyglotEngine#eval(com.oracle.truffle.api.source.Source) * 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#fromFileName file on a disk} changes and is * re-loaded. * * @return a URI, it's never null * @since 0.14 */ public URI getURI() { return content().getURI(); } /** * Access to the source contents. * * @since 0.8 or earlier */ public Reader getReader() { try { return content().getReader(); } catch (final IOException ex) { return new Reader() { @Override public int read(char[] cbuf, int off, int len) throws IOException { throw ex; } @Override public void close() throws IOException { } }; } } /** * Access to the source contents. * * @since 0.8 or earlier */ public final InputStream getInputStream() { return new ByteArrayInputStream(getCode().getBytes()); } /** * Gets the number of characters in the source. * * @since 0.8 or earlier */ public final int getLength() { return getTextMap().length(); } /** * Returns the complete text of the code. * * @since 0.8 or earlier */ public String getCode() { return content().getCode(); } /** * Returns a subsection of the code test. * * @since 0.8 or earlier */ public String getCode(int charIndex, int charLength) { return getCode().substring(charIndex, charIndex + charLength); } /** * Gets the text (not including a possible terminating newline) in a (1-based) numbered line. * * @since 0.8 or earlier */ public final String getCode(int lineNumber) { final int offset = getTextMap().lineStartOffset(lineNumber); final int length = getTextMap().lineLength(lineNumber); return getCode().substring(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. * * @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. * * @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. * * @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 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. * * @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); } /** * Append text to a Source explicitly created as Appendable. * * @param chars the text to append * @throws UnsupportedOperationException by concrete subclasses that do not support appending * @since 0.8 or earlier */ public void appendCode(CharSequence chars) { content().appendCode(chars); clearTextMap(); } /** * Creates a representation of a contiguous region of text in the source. *

* This method performs no checks on the validity of the arguments. *

* The resulting representation defines hash/equality around equivalent location, presuming that * {@link Source} representations are canonical. * * @param identifier terse description of the region * @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 charIndex the 0-based index of the first character of the section * @param length the number of characters in the section * @return newly created object representing the specified region * @since 0.8 or earlier */ public final SourceSection createSection(String identifier, int startLine, int startColumn, int charIndex, int length) { checkRange(charIndex, length); return createSectionImpl(identifier, startLine, startColumn, charIndex, length, SourceSection.EMTPY_TAGS); } /** * @deprecated tags are now determined by {@link Node#isTaggedWith(Class)}. Use * {@link #createSection(String, int, int, int, int)} instead. * @since 0.12 */ @Deprecated public final SourceSection createSection(String identifier, int startLine, int startColumn, int charIndex, int length, String... tags) { checkRange(charIndex, length); return createSectionImpl(identifier, startLine, startColumn, charIndex, length, tags); } private SourceSection createSectionImpl(String identifier, int startLine, int startColumn, int charIndex, int length, String[] tags) { return new SourceSection(null, this, identifier, startLine, startColumn, charIndex, length, tags); } /** * Creates a representation of a contiguous region of text in the source. Computes the * {@code charIndex} value by building a {@code TextMap map} of lines in the source. *

* Checks the position arguments for consistency with the source. *

* The resulting representation defines hash/equality around equivalent location, presuming that * {@link Source} representations are canonical. * * @param identifier terse description of the region * @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 IllegalArgumentException if arguments are outside the text of the source * @throws IllegalStateException if the source is one of the "null" instances * @since 0.8 or earlier */ public final SourceSection createSection(String identifier, int startLine, int startColumn, int length) { final int lineStartOffset = getTextMap().lineStartOffset(startLine); if (startColumn > getTextMap().lineLength(startLine)) { throw new IllegalArgumentException("column out of range"); } final int startOffset = lineStartOffset + startColumn - 1; return createSectionImpl(identifier, startLine, startColumn, startOffset, length, SourceSection.EMTPY_TAGS); } /** * Creates a representation of a contiguous region of text in the source. Computes the * {@code (startLine, startColumn)} values by building a {@code TextMap map} of lines in the * source. *

* Checks the position arguments for consistency with the source. *

* The resulting representation defines hash/equality around equivalent location, presuming that * {@link Source} representations are canonical. * * * @param identifier terse description of the region * @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 IllegalArgumentException if either of the arguments are outside the text of the * source * @throws IllegalStateException if the source is one of the "null" instances * @since 0.8 or earlier */ public final SourceSection createSection(String identifier, int charIndex, int length) throws IllegalArgumentException { return createSection(identifier, charIndex, length, SourceSection.EMTPY_TAGS); } /** * @deprecated tags are now determined by {@link Node#isTaggedWith(Class)}. Use * {@link #createSection(String, int, int)} instead. * @since 0.12 */ @Deprecated public final SourceSection createSection(String identifier, int charIndex, int length, String... tags) throws IllegalArgumentException { checkRange(charIndex, length); final int startLine = getLineNumber(charIndex); final int startColumn = charIndex - getLineStartOffset(startLine) + 1; return createSectionImpl(identifier, startLine, startColumn, charIndex, length, tags); } void checkRange(int charIndex, int length) { if (!(charIndex >= 0 && length >= 0 && charIndex + length <= getCode().length())) { throw new IllegalArgumentException("text positions out of range"); } } /** * Creates a representation of a line of text in the source identified only by line number, from * which the character information will be computed. * * @param identifier terse description of the line * @param lineNumber 1-based line number of the first character in the section * @return newly created object representing the specified line * @throws IllegalArgumentException if the line does not exist the source * @throws IllegalStateException if the source is one of the "null" instances * @since 0.8 or earlier */ public final SourceSection createSection(String identifier, int lineNumber) { final int charIndex = getTextMap().lineStartOffset(lineNumber); final int length = getTextMap().lineLength(lineNumber); return createSection(identifier, charIndex, length); } /** * Creates a representation of a line number in this source, suitable for use as a hash table * key with equality defined to mean equivalent location. * * @param lineNumber a 1-based line number in this source * @return a representation of a line in this source * @since 0.8 or earlier */ public final LineLocation createLineLocation(int lineNumber) { return new LineLocation(this, lineNumber); } /** * An object suitable for using as a key into a hashtable that defines equivalence between * different source types. */ Object getHashKey() { return content().getHashKey(); } final TextMap getTextMap() { if (textMap == null) { textMap = createTextMap(); } return textMap; } final void clearTextMap() { textMap = null; } TextMap createTextMap() { final String code = getCode(); if (code == null) { throw new RuntimeException("can't read file " + getName()); } return TextMap.fromString(code); } /** * Associates the source with specified MIME type. The mime type may be used to select the right * {@link Registration Truffle language} to use to execute the returned source. The value of * MIME type can be obtained via {@link #getMimeType()} method. * * @param mime mime type to use * @return new (identical) source, just associated {@link #getMimeType()} * @since 0.8 or earlier */ public final Source withMimeType(String mime) { try { Source another = (Source) clone(); another.mimeType = mime; return another; } catch (CloneNotSupportedException ex) { throw new IllegalStateException(ex); } } /** * Associates the source with specified name. * * @param newName name to be returned from {@link #getName()} method * @return new (identical) source, with changed {@link #getName()} * @since 0.14 */ final Source withName(String newName) { try { Source another = (Source) clone(); another.name = newName; return another; } catch (CloneNotSupportedException ex) { throw new IllegalStateException(ex); } } /** * Associates the source with specified short name. * * @param newShortName name to be returned from {@link #getShortName()} method * @return new (identical) source, with changed {@link #getShortName()} * @since 0.14 */ final Source withShortName(String newShortName) { try { Source another = (Source) clone(); another.shortName = newShortName; return another; } catch (CloneNotSupportedException ex) { throw new IllegalStateException(ex); } } /** * Associates the source with specified short name. * * @param newPath name to be returned from {@link #getPath()} method * @return new (identical) source, with changed {@link #getPath()} * @since 0.14 */ final Source withPath(String newPath) { try { Source another = (Source) clone(); another.path = newPath; return another; } catch (CloneNotSupportedException ex) { throw new IllegalStateException(ex); } } /** * MIME type that is associated with this source. By default file extensions known to the system * are used to determine the MIME type (via registered {@link FileTypeDetector} classes), yet * one can directly {@link #withMimeType(java.lang.String) provide a MIME type} to each source. * * @return MIME type of this source or null, if unknown * @since 0.8 or earlier */ public String getMimeType() { if (mimeType == null) { try { mimeType = content().findMimeType(); } catch (IOException ex) { // swallow and return null } } return mimeType; } final boolean equalAttributes(Source other) { return Objects.equals(getMimeType(), other.getMimeType()) && Objects.equals(getName(), other.getName()) && Objects.equals(getShortName(), other.getShortName()) && Objects.equals(getPath(), other.getPath()); } } // @formatter:off 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 'c:\\sources\\Example.java' file"; Source source = Source.fromFileName(file.getPath()); assert file.getName().equals(source.getShortName()); assert file.getPath().equals(source.getPath()); assert file.getPath().equals(source.getName()); assert file.toURI().equals(source.getURI()); assert "text/x-java".equals(source.getMimeType()); // END: SourceSnippets#fromFile return source; } public static Source fromURL() throws IOException, URISyntaxException { // BEGIN: SourceSnippets#fromURL URL resource = SourceSnippets.class.getResource("sample.js"); Source source = Source.fromURL(resource, "/your/pkg/sample.js"); assert "/your/pkg/sample.js".equals(source.getShortName()); assert resource.toExternalForm().equals(source.getPath()); assert "/your/pkg/sample.js".equals(source.getName()); assert "application/javascript".equals(source.getMimeType()); assert resource.toURI().equals(source.getURI()); // END: SourceSnippets#fromURL return source; } public static Source fromReader() throws IOException { // BEGIN: SourceSnippets#fromReader Reader stream = new InputStreamReader( SourceSnippets.class.getResourceAsStream("sample.js") ); Source source = Source.fromReader(stream, "/your/pkg/sample.js"); assert "/your/pkg/sample.js".equals(source.getShortName()); assert "/your/pkg/sample.js".equals(source.getPath()); assert "/your/pkg/sample.js".equals(source.getName()); assert null == source.getMimeType(); // END: SourceSnippets#fromReader return source; } public static Source fromAString() throws Exception { // BEGIN: SourceSnippets#fromAString Source source = Source.fromText("function() {\n" + " return 'Hi';\n" + "}\n", "/my/scripts/hi.js" ); assert "/my/scripts/hi.js".equals(source.getShortName()); assert "/my/scripts/hi.js".equals(source.getPath()); assert "/my/scripts/hi.js".equals(source.getName()); assert null == source.getMimeType() : "No mime type associated"; // END: SourceSnippets#fromAString return source; } } // @formatter:on





© 2015 - 2024 Weber Informatics LLC | Privacy Policy