changeset 19697:191c55f08ed2

Truffle: add the ability to "tag" Sources with any number of standard or private tags, for example so that Sources might be marked as "FROM_FILE", "LIBRARY", "BUILTIN", or any other distinction that matters to some tools. Those tags can be applied by the language runtime when sources are created, for example when loading builtins. Alternately, you can listen for newly created sources from outside the implementation, where you might tag sources based on pattern matching against file paths or any other meta-information in the Source.
author Michael Van De Vanter <michael.van.de.vanter@oracle.com>
date Wed, 04 Mar 2015 16:36:27 -0800
parents 27fe86a6fb49
children 32b4b06b6fac
files graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceTest.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceListener.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceTag.java graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/StandardSourceTag.java
diffstat 5 files changed, 455 insertions(+), 4 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api.test/src/com/oracle/truffle/api/test/source/SourceTest.java	Wed Mar 04 16:36:27 2015 -0800
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2015, 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.
+ *
+ * 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.test.source;
+
+import static org.junit.Assert.*;
+
+import org.junit.*;
+
+import com.oracle.truffle.api.source.*;
+
+public class SourceTest {
+
+    @Test
+    public void sourceTagTest() {
+
+        // Private tag
+        final SourceTag testTag = new SourceTag() {
+
+            public String name() {
+                return null;
+            }
+
+            public String getDescription() {
+                return null;
+            }
+        };
+
+        // No sources exist with the private tag
+        assertEquals(Source.findSourcesTaggedAs(testTag).size(), 0);
+
+        // Create a new source
+        final Source source = Source.fromText("test1 source", "test1 source");
+
+        // Initially has only the default tag
+        assertEquals(source.getSourceTags().size(), 1);
+
+        assertTrue(source.getSourceTags().contains(StandardSourceTag.FROM_LITERAL));
+        assertTrue(source.isTaggedAs(StandardSourceTag.FROM_LITERAL));
+        assertTrue(Source.findSourcesTaggedAs(StandardSourceTag.FROM_LITERAL).contains(source));
+
+        assertFalse(source.isTaggedAs(testTag));
+        assertEquals(Source.findSourcesTaggedAs(testTag).size(), 0);
+
+        // Add a private tag
+        source.tagAs(testTag);
+
+        // Now there are exactly two tags
+        assertEquals(source.getSourceTags().size(), 2);
+
+        assertTrue(source.getSourceTags().contains(StandardSourceTag.FROM_LITERAL));
+        assertTrue(source.isTaggedAs(StandardSourceTag.FROM_LITERAL));
+        assertTrue(Source.findSourcesTaggedAs(StandardSourceTag.FROM_LITERAL).contains(source));
+
+        assertTrue(source.getSourceTags().contains(testTag));
+        assertTrue(source.isTaggedAs(testTag));
+        assertEquals(Source.findSourcesTaggedAs(testTag).size(), 1);
+        assertTrue(Source.findSourcesTaggedAs(testTag).contains(source));
+
+        // Add the private tag again
+        source.tagAs(testTag);
+
+        // Nothing has changed
+        assertEquals(source.getSourceTags().size(), 2);
+
+        assertTrue(source.getSourceTags().contains(StandardSourceTag.FROM_LITERAL));
+        assertTrue(source.isTaggedAs(StandardSourceTag.FROM_LITERAL));
+        assertTrue(Source.findSourcesTaggedAs(StandardSourceTag.FROM_LITERAL).contains(source));
+
+        assertTrue(source.getSourceTags().contains(testTag));
+        assertTrue(source.isTaggedAs(testTag));
+        assertEquals(Source.findSourcesTaggedAs(testTag).size(), 1);
+        assertTrue(Source.findSourcesTaggedAs(testTag).contains(source));
+    }
+
+    @Test
+    public void sourceListenerTest() {
+
+        // Private tag
+        final SourceTag testTag = new SourceTag() {
+
+            public String name() {
+                return null;
+            }
+
+            public String getDescription() {
+                return null;
+            }
+        };
+
+        final int[] newSourceEvents = {0};
+        final Source[] newSource = {null};
+
+        final int[] newTagEvents = {0};
+        final Source[] taggedSource = {null};
+        final SourceTag[] newTag = {null};
+
+        Source.addSourceListener(new SourceListener() {
+
+            public void sourceCreated(Source source) {
+                newSourceEvents[0] = newSourceEvents[0] + 1;
+                newSource[0] = source;
+            }
+
+            public void sourceTaggedAs(Source source, SourceTag tag) {
+                newTagEvents[0] = newTagEvents[0] + 1;
+                taggedSource[0] = source;
+                newTag[0] = tag;
+            }
+        });
+
+        // New source has a default tag applied.
+        // Get one event for the new source, another one when it gets tagged
+        final Source source = Source.fromText("testSource", "testSource");
+        assertEquals(newSourceEvents[0], 1);
+        assertEquals(newSource[0], source);
+        assertEquals(newTagEvents[0], 1);
+        assertEquals(taggedSource[0], source);
+        assertEquals(newTag[0], StandardSourceTag.FROM_LITERAL);
+
+        // reset
+        newSource[0] = null;
+        taggedSource[0] = null;
+        newTag[0] = null;
+
+        // Add a tag; only get one event (the new tag)
+        source.tagAs(testTag);
+        assertEquals(newSourceEvents[0], 1);
+        assertEquals(newSource[0], null);
+        assertEquals(newTagEvents[0], 2);
+        assertEquals(taggedSource[0], source);
+        assertEquals(newTag[0], testTag);
+
+        // Add the same tag; no events, and nothing changes.
+        source.tagAs(testTag);
+        assertEquals(newSourceEvents[0], 1);
+        assertEquals(newSource[0], null);
+        assertEquals(newTagEvents[0], 2);
+        assertEquals(taggedSource[0], source);
+        assertEquals(newTag[0], testTag);
+
+    }
+}
--- a/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Wed Mar 04 16:28:05 2015 -0800
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java	Wed Mar 04 16:36:27 2015 -0800
@@ -29,6 +29,8 @@
 import java.net.*;
 import java.util.*;
 
+import com.oracle.truffle.api.instrument.*;
+
 /**
  * Representation of a guest language source code unit and its contents. Sources originate in
  * several ways:
@@ -66,17 +68,28 @@
  * <li>Any access to file contents via the cache will result in a timestamp check and possible cache
  * reload.</li>
  * </ol>
+ * <p>
+ *
+ * @see SourceTag
+ * @see SourceListener
  */
 public abstract class Source {
 
     // TODO (mlvdv) consider canonicalizing and reusing SourceSection instances
     // TOOD (mlvdv) connect SourceSections into a spatial tree for fast geometric lookup
 
+    /**
+     * All Sources that have been created.
+     */
+    private static final List<WeakReference<Source>> allSources = new ArrayList<>();
+
     // Files and pseudo files are indexed.
     private static final Map<String, WeakReference<Source>> filePathToSource = new Hashtable<>();
 
     private static boolean fileCacheEnabled = true;
 
+    private static final List<SourceListener> sourceListeners = new ArrayList<>();
+
     /**
      * Gets the canonical representation of a source file, whose contents will be read lazily and
      * then cached.
@@ -106,6 +119,7 @@
         if (reset) {
             source.reset();
         }
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_FILE);
         return source;
     }
 
@@ -147,6 +161,7 @@
                 filePathToSource.put(path, new WeakReference<>(source));
             }
         }
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_FILE);
         return source;
     }
 
@@ -160,7 +175,9 @@
      */
     public static Source fromText(CharSequence chars, String description) {
         assert chars != null;
-        return new LiteralSource(description, chars.toString());
+        final LiteralSource source = new LiteralSource(description, chars.toString());
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_LITERAL);
+        return source;
     }
 
     /**
@@ -172,7 +189,9 @@
      * @throws IOException if reading fails
      */
     public static Source fromURL(URL url, String description) throws IOException {
-        return URLSource.get(url, description);
+        final URLSource source = URLSource.get(url, description);
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_URL);
+        return source;
     }
 
     /**
@@ -184,7 +203,9 @@
      * @throws IOException if reading fails
      */
     public static Source fromReader(Reader reader, String description) throws IOException {
-        return new LiteralSource(description, read(reader));
+        final LiteralSource source = new LiteralSource(description, read(reader));
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_READER);
+        return source;
     }
 
     /**
@@ -215,7 +236,9 @@
      * @return a newly created, non-indexed source representation
      */
     public static Source fromBytes(byte[] bytes, int byteIndex, int length, String description, BytesDecoder decoder) {
-        return new BytesSource(description, bytes, byteIndex, length, decoder);
+        final BytesSource source = new BytesSource(description, bytes, byteIndex, length, decoder);
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_BYTES);
+        return source;
     }
 
     /**
@@ -229,6 +252,7 @@
     public static Source asPseudoFile(CharSequence chars, String pseudoFileName) {
         final Source source = new LiteralSource(pseudoFileName, chars.toString());
         filePathToSource.put(pseudoFileName, new WeakReference<>(source));
+        notifyNewSource(source).tagAs(StandardSourceTag.FROM_LITERAL);
         return source;
     }
 
@@ -241,6 +265,48 @@
         fileCacheEnabled = enabled;
     }
 
+    /**
+     * Returns all {@link Source}s holding a particular {@link SyntaxTag}, or the whole collection
+     * of Sources if the specified tag is {@code null}.
+     *
+     * @return A collection of Sources containing the given tag.
+     */
+    public static Collection<Source> findSourcesTaggedAs(SourceTag tag) {
+        final List<Source> taggedSources = new ArrayList<>();
+        for (WeakReference<Source> ref : allSources) {
+            Source source = ref.get();
+            if (source != null) {
+                if (tag == null || source.isTaggedAs(tag)) {
+                    taggedSources.add(ref.get());
+                }
+            }
+        }
+        return taggedSources;
+    }
+
+    /**
+     * Adds a {@link SourceListener} to receive events.
+     */
+    public static void addSourceListener(SourceListener listener) {
+        assert listener != null;
+        sourceListeners.add(listener);
+    }
+
+    /**
+     * Removes a {@link SourceListener}. Ignored if listener not found.
+     */
+    public static void removeSourceListener(SourceListener listener) {
+        sourceListeners.remove(listener);
+    }
+
+    private static Source notifyNewSource(Source source) {
+        allSources.add(new WeakReference<>(source));
+        for (SourceListener listener : sourceListeners) {
+            listener.sourceCreated(source);
+        }
+        return source;
+    }
+
     private static String read(Reader reader) throws IOException {
         final BufferedReader bufferedReader = new BufferedReader(reader);
         final StringBuilder builder = new StringBuilder();
@@ -257,6 +323,8 @@
         return builder.toString();
     }
 
+    private final ArrayList<SourceTag> tags = new ArrayList<>();
+
     Source() {
     }
 
@@ -264,6 +332,32 @@
 
     protected abstract void reset();
 
+    public final boolean isTaggedAs(SourceTag tag) {
+        assert tag != null;
+        return tags.contains(tag);
+    }
+
+    public final Collection<SourceTag> getSourceTags() {
+        return Collections.unmodifiableCollection(tags);
+    }
+
+    /**
+     * Adds a {@linkplain SourceTag tag} to the set of tags associated with this {@link Source};
+     * {@code no-op} if already in the set.
+     *
+     * @return this
+     */
+    public final Source tagAs(SourceTag tag) {
+        assert tag != null;
+        if (!tags.contains(tag)) {
+            tags.add(tag);
+            for (SourceListener listener : sourceListeners) {
+                listener.sourceTaggedAs(this, tag);
+            }
+        }
+        return this;
+    }
+
     /**
      * Returns the name of this resource holding a guest language program. An example would be the
      * name of a guest language source code file.
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceListener.java	Wed Mar 04 16:36:27 2015 -0800
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2015, 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;
+
+/**
+ * An observer of events related to {@link Source}s: creating and tagging.
+ */
+public interface SourceListener {
+
+    /**
+     * Notifies that a new {@link Source} has just been created.
+     */
+    void sourceCreated(Source source);
+
+    /**
+     * Notifies that a {@link SourceTag} has been newly added to the set of tags associated with a
+     * {@link Source} via {@link Source#tagAs(SourceTag)}.
+     * <p>
+     * The {@linkplain SourceTag tags} at a {@link Source} are a <em>set</em>; this notification
+     * will only be delivered the first time a particular {@linkplain SourceTag tag} is added at a
+     * {@link Source}.
+     *
+     * @param source where a tag has been added
+     * @param tag the tag that has been newly added (subsequent additions of the tag are
+     *            unreported).
+     */
+    void sourceTaggedAs(Source source, SourceTag tag);
+
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/SourceTag.java	Wed Mar 04 16:36:27 2015 -0800
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2015, 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;
+
+/**
+ * Categorical information (best implemented as enums} about particular sources of Guest Language
+ * code that can be useful to configure behavior of both the language runtime and external tools.
+ * These might include {@linkplain StandardSourceTag standard tags} noting, for example, whether the
+ * source was read from a file and whether it should be considered library code.
+ * <p>
+ * An untagged {@link Source} should by default be considered application code.
+ * <p>
+ * The need for additional tags is likely to arise, in some cases because of issue specific to a
+ * Guest Language, but also for help configuring the behavior of particular tools.
+ *
+ * @see Source
+ * @see StandardSourceTag
+ */
+public interface SourceTag {
+
+    /**
+     * Human-friendly name of a category of code sources, e.g. "file", or "library".
+     *
+     */
+    String name();
+
+    /**
+     * Criteria and example uses for the tag.
+     */
+    String getDescription();
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/StandardSourceTag.java	Wed Mar 04 16:36:27 2015 -0800
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2015, 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;
+
+/**
+ * A general set of "properties" or "categories" that might be usefully attached to a particular
+ * source of code, both for use by the language runtime and by external tools. This set of tags
+ * includes some intended to be applied by default by {@link Source} factory methods or other
+ * services built into the Truffle platform.
+ * <p>
+ * The need for additional tags is likely to arise, in some cases because of issue specific to a
+ * Guest Language, but also for help configuring the behavior of particular tools.
+ *
+ * @see Source
+ */
+public enum StandardSourceTag implements SourceTag {
+
+    /**
+     * Builtin.
+     */
+    BUILTIN("builtin", "implementation of language builtins"),
+
+    /**
+     * From bytes.
+     */
+    FROM_BYTES("bytes", "read from bytes"),
+
+    /**
+     * Read from a file.
+     */
+    FROM_FILE("file", "read from a file"),
+
+    /**
+     * From literal text.
+     */
+    FROM_LITERAL("literal", "from literal text"),
+
+    /**
+     * From a {@linkplain java.io.Reader Reader}.
+     */
+    FROM_READER("reader", "read from a Java Reader"),
+
+    /**
+     * Read from a URL.
+     */
+    FROM_URL("URL", "read from a URL"),
+
+    /**
+     * Treat as LIBRARY code.
+     */
+    LIBRARY("library", "library code");
+
+    private final String name;
+    private final String description;
+
+    private StandardSourceTag(String name, String description) {
+        this.name = name;
+        this.description = description;
+    }
+
+    public String getName() {
+        return name;
+    }
+
+    public String getDescription() {
+        return description;
+    }
+
+}