Mercurial > hg > graal-jvmci-8
diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java @ 13455:69d2e4baa215
Truffle: new infrastructure related to instrumentation, and in particular debugging: support for managing Source objects; framework for generalized "instrumentation proxy nodes" (to be inserted into ASTs with no runtime cost when inactive), and "probes" (which can be attached to proxy nodes to receive event notification); a rudimentary interface and abstract implementation for a "debug manager" (mostly a placeholder at this point); and the beginning of a language-agnostic ExecutionContext interface.
author | Michael Van De Vanter <michael.van.de.vanter@oracle.com> |
---|---|
date | Tue, 17 Dec 2013 20:22:45 -0800 |
parents | |
children | f76593e3fedb |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceManager.java Tue Dec 17 20:22:45 2013 -0800 @@ -0,0 +1,323 @@ +/* + * Copyright (c) 2013, 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.*; +import java.util.*; + +import com.oracle.truffle.api.*; + +/** + * A representation of source code information, suitable for hash table keys with equality defined + * in terms of content. There are three kinds of sources supported at present. + * <ul> + * <li><strong>File:</strong>Each file is represented as a canonical object, indexed by the + * absolute, canonical path name of the file. The textual contents of the file may be supplied when + * the object is created, or it may be read lazily. Only one lazy attempt will be made to read a + * file, and failure will result silently in null content.</li> + * <li><strong>Literal Source:</strong> A named text string, whose contents are supplied concretely + * (possibly via an {@link InputStream}), can also be used as a source. These are represented as + * value objects whose equality depends on both name and contents.</li> + * <li><strong>Fake Files:</strong> A named text string used for testing; its contents can be + * retrieved by name, unlike literal sources.</li> + * </ul> + */ +public final class SourceManager { + + // Only files and fake files are indexed. + private final Map<String, SourceImpl> sourceMap = new HashMap<>(); + + public SourceManager() { + + } + + /** + * Gets the canonical representation of a source file, whose contents will be read lazily and + * then cached. + * + * @param reset forces any existing {@link Source} cache to be cleared, forcing a re-read. + */ + public Source get(String fileName, boolean reset) { + SourceImpl source = sourceMap.get(fileName); + if (source == null) { + String path = findPath(fileName); + if (path == null) { + throw new RuntimeException("Can't find file " + fileName); + } + source = sourceMap.get(path); + if (source == null) { + source = new FileSourceImpl(fileName, path); + sourceMap.put(path, source); + } + } else { + if (reset) { + source.reset(); + } + } + return source; + } + + /** + * Gets the canonical representation of a source file, whose contents will be read lazily and + * then cached. + */ + public Source get(String fileName) { + return get(fileName, false); + } + + /** + * Creates a source from literal text. + */ + @SuppressWarnings("static-method") + public Source get(String name, String code) { + assert code != null; + return new LiteralSourceImpl(name, code); + } + + /** + * Creates a source whose contents will be read immediately and cached. + */ + @SuppressWarnings("static-method") + public Source get(String name, InputStream stream) throws IOException { + InputStreamReader reader = new InputStreamReader(stream); + return new LiteralSourceImpl(name, readCode(reader)); + } + + /** + * Creates a source from literal text, but which acts as a file and can be retrieved by name + * (unlike other literal sources); intended for testing. + */ + public Source getFakeFile(String name, String code) { + final SourceImpl source = new LiteralSourceImpl(name, code); + sourceMap.put(name, source); + return source; + } + + // If it names a real file, get the (canonical) normalized absolute path. + private static String findPath(String name) { + final File file = new File(name); + if (file.exists()) { + try { + return file.getCanonicalPath(); + } catch (IOException e) { + } + } + return null; + } + + private static String readCode(Reader reader) throws IOException { + final StringBuilder builder = new StringBuilder(); + final char[] buffer = new char[1024]; + + while (true) { + final int n = reader.read(buffer); + if (n == -1) { + break; + } + builder.append(buffer, 0, n); + } + + return builder.toString(); + } + + private abstract static class SourceImpl implements Source { + + protected TextMap textMap = null; + + protected abstract void reset(); + + public final InputStream getInputStream() { + return new ByteArrayInputStream(getCode().getBytes()); + } + + /** + * Gets the text (not including a possible terminating newline) in a (1-based) numbered + * line. + */ + public final String getCode(int lineNumber) { + checkTextMap(); + final int offset = textMap.lineStartOffset(lineNumber); + final int length = textMap.lineLength(lineNumber); + return getCode().substring(offset, offset + length); + } + + /** + * The number of text lines in the source. + */ + public final int getLineCount() { + return checkTextMap().lineCount(); + } + + /** + * The 1-based number of the line that includes a 0-based character offset. + */ + public final int getLineNumber(int offset) { + return checkTextMap().offsetToLine(offset); + } + + /** + * The 0-based character offset at the start of a (1-based) numbered line. + */ + public final int getLineStartOffset(int lineNumber) { + return checkTextMap().lineStartOffset(lineNumber); + } + + /** + * The number of characters (not counting a possible terminating newline) in a (1-based) + * numbered line. + */ + public final int getLineLength(int lineNumber) { + return checkTextMap().lineLength(lineNumber); + } + + private TextMap checkTextMap() { + if (textMap == null) { + final String code = getCode(); + if (code == null) { + throw new RuntimeException("can't read file " + getName()); + } + textMap = new TextMap(code); + } + return textMap; + } + } + + public static class LiteralSourceImpl extends SourceImpl { + + private final String name; // Name used originally to describe the source + private final String code; + + public LiteralSourceImpl(String name, String code) { + this.name = name; + this.code = code; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getCode() { + return code; + } + + @Override + public String getPath() { + return name; + } + + @Override + public Reader getReader() { + return new StringReader(code); + } + + @Override + protected void reset() { + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + name.hashCode(); + result = prime * result + (code == null ? 0 : code.hashCode()); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (!(obj instanceof LiteralSourceImpl)) { + return false; + } + LiteralSourceImpl other = (LiteralSourceImpl) obj; + return name.equals(other.name) && code.equals(other.code); + } + + } + + private static class FileSourceImpl extends SourceImpl { + + private final String name; // Name used originally to describe the source + private String code = null; + private final String path; // Normalized path description of an actual file + private boolean readAttempted; + + public FileSourceImpl(String name, String path) { + this.name = name; + this.path = path; + this.readAttempted = false; + + } + + @Override + public String getName() { + return name; + } + + @Override + public String getCode() { + if (code == null && !readAttempted) { + readAttempted = true; + try { + code = readCode(getReader()); + } catch (IOException e) { + } + } + return code; + } + + @Override + public String getPath() { + return path; + } + + @Override + public Reader getReader() { + if (code != null) { + return new StringReader(code); + } + try { + return new BufferedReader(new FileReader(path)); + } catch (FileNotFoundException e) { + throw new RuntimeException("Can't find file " + path); + } + } + + @Override + protected void reset() { + this.code = null; + this.readAttempted = false; + } + + } + +}