diff graal/com.oracle.truffle.api/src/com/oracle/truffle/api/source/Source.java @ 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 c152a485d747
children eebf140fa6e4
line wrap: on
line diff
--- 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.