87b78d6ebf44434c61a252baf8fc339886bc9d1f
[simantics/platform.git] / bundles / org.simantics.scl.osgi / src / org / simantics / scl / osgi / internal / BundleModuleSource.java
1 package org.simantics.scl.osgi.internal;
2
3
4 import java.io.File;
5 import java.io.IOException;
6 import java.io.InputStream;
7 import java.net.URL;
8 import java.nio.charset.Charset;
9 import java.nio.file.Files;
10 import java.nio.file.Path;
11 import java.security.MessageDigest;
12 import java.security.NoSuchAlgorithmException;
13 import java.util.Arrays;
14
15 import org.eclipse.core.runtime.FileLocator;
16 import org.osgi.framework.Bundle;
17 import org.osgi.framework.wiring.BundleWiring;
18 import org.simantics.scl.compiler.internal.codegen.types.JavaReferenceValidatorFactory;
19 import org.simantics.scl.compiler.module.ImportDeclaration;
20 import org.simantics.scl.compiler.module.repository.UpdateListener;
21 import org.simantics.scl.compiler.source.EncodedTextualModuleSource;
22 import org.simantics.scl.compiler.types.Type;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 import gnu.trove.set.hash.THashSet;
27
28 public class BundleModuleSource extends EncodedTextualModuleSource implements UpdateListener.Observable {
29
30     private static final Logger LOGGER = LoggerFactory.getLogger(BundleModuleSource.class);
31
32     public static final ImportDeclaration[] DEFAULT_IMPORTS = new ImportDeclaration[] {
33         new ImportDeclaration("Builtin", ""),
34         new ImportDeclaration("StandardLibrary", "")
35     };
36     
37     public static final ImportDeclaration[] DEFAULT_IMPORTS_FOR_STANDARD_LIBRARY = new ImportDeclaration[] {
38         new ImportDeclaration("Builtin", ""),
39     };
40     
41     public final Bundle bundle;
42     public final URL url;
43     
44     private byte[] digest;
45     private THashSet<UpdateListener> listeners;
46     
47     public BundleModuleSource(String moduleName, Bundle bundle, URL url) {
48         super(moduleName);
49         this.bundle = bundle;
50         this.url = url;
51     }
52     
53     @Override
54     public void removeListener(UpdateListener listener) {
55         if(listeners != null)
56             synchronized(listeners) {
57                 listeners.remove(listener);
58             }
59     }
60
61     @Override
62     public ImportDeclaration[] getBuiltinImports(UpdateListener listener) {
63         if(bundle.getSymbolicName().equals("org.simantics.scl.runtime"))
64             return DEFAULT_IMPORTS_FOR_STANDARD_LIBRARY;
65         else
66             return DEFAULT_IMPORTS;
67     }
68     
69     private byte[] computeDigest() {
70         try {
71             InputStream stream = url.openStream();
72             try {
73                 MessageDigest digest = MessageDigest.getInstance("SHA1");
74                 byte[] buffer = new byte[1024];
75                 while(true) {
76                     int count = stream.read(buffer);
77                     if(count <= 0)
78                         break;
79                     digest.update(buffer, 0, count);
80                 }
81                 return digest.digest();
82             } catch (NoSuchAlgorithmException e) {
83                 LOGGER.error("No SHA1 algorithm available", e);
84                 return new byte[0];
85             } finally {
86                 stream.close();
87             }
88         } catch(IOException e) {
89             LOGGER.error("Could not compute digest for {}", this);
90             return new byte[0];
91         }
92     }
93     
94     @Override
95     protected InputStream getSourceStream(UpdateListener listener)
96             throws IOException {
97         if(digest == null)
98             digest = computeDigest();
99         if(listener != null) {
100             if(listeners == null)
101                 listeners = new THashSet<UpdateListener>(4);
102             listeners.add(listener);
103             listener.addObservable(this);
104         }
105         return url.openStream();
106     }
107     
108     @Override
109     public ClassLoader getClassLoader() {
110         if(bundle.getSymbolicName().equals("org.simantics.scl.runtime"))
111             return Type.class.getClassLoader();
112         else {
113             BundleWiring wiring = bundle.adapt(BundleWiring.class);
114             if(wiring != null)
115                 return wiring.getClassLoader();
116             else
117                 return getClass().getClassLoader();
118         }
119     }
120
121     public void checkUpdates() {
122         if(digest != null && listeners != null) {
123             byte[] newDigest = computeDigest();
124             if(!Arrays.equals(digest, newDigest)) {
125                 digest = newDigest;
126                 THashSet<UpdateListener> oldListeners = listeners;
127                 listeners = null;
128                 for(UpdateListener listener : oldListeners)
129                     listener.notifyAboutUpdate();
130             }
131         }
132     }
133
134     /*
135      * This code is a copy from org.simantics.utils.ui.BundleUtils
136      */
137     public static File resolveWritableBundleFile(URL url) throws IOException {
138         // This returns file, jar, http etc. - essentially resolves the bundle protocol
139         URL resolved = FileLocator.resolve(url);
140         if (resolved.getProtocol().equals("file")) {
141             return new File(resolved.getPath());
142         }
143         return null;
144     }
145
146     private Path getPath() throws IOException {
147         File file = resolveWritableBundleFile(url);
148         return file != null ? file.toPath() : null;
149     }
150
151     @Override
152     public boolean isUpdateable() {
153         try {
154             Path path = getPath();
155             if(path == null)
156                 return false;
157             return Files.exists(path);
158         } catch (IOException e) {
159             LOGGER.debug("Could not check if {} is updateable", this, e);
160             return false;
161         }
162     }
163
164     @Override
165     public void update(String newSourceText) {
166         try {
167             Path path = getPath();
168             if(path == null)
169                 return;
170             Files.write(path, newSourceText.getBytes(Charset.forName("UTF-8")));
171         } catch(IOException e) {
172             LOGGER.error("Could not update {} with {}", this, newSourceText, e);
173         }
174         checkUpdates();
175     }
176
177     public void clear() {
178         if (listeners != null) {
179             listeners.clear();
180             listeners = null;
181         }
182     }
183
184     public JavaReferenceValidatorFactory getJavaReferenceValidatorFactory() {
185         return new OsgiJavaReferenceValidatorFactory(bundle);
186     }
187 }