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