package nebula.lang; import java.io.File; import java.io.IOException; import java.io.Reader; import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; import java.util.List; import java.util.Map; import org.antlr.runtime.RecognitionException; import util.FileUtil; import com.google.common.collect.Lists; import com.google.common.collect.Maps; public class EditableTypeLoader extends TypeLoader implements Runnable { final List<File> paths; final Map<URL, Type> loadedTypes; public EditableTypeLoader(TypeLoader parent, File... paths) { super(parent); this.paths = Lists.newCopyOnWriteArrayList(); for (File file : paths) { this.paths.add(file); } loadedTypes = Maps.newHashMap(); } public void loadAllImmediately(){ for (File path : this.paths) { this.doLoadFolder(path, path); } } public void start() { new Thread(this).start(); } @Override public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { log.error(e); throw new RuntimeException(e); } for (File path : this.paths) { this.doLoadFolder(path, path); } } public void registerPath(File folder) { this.paths.add(folder); if (log.isDebugEnabled()) { log.debug("register folder - " + folder.getPath()); } } private void doLoadFolder(File root, File d) { try { if (!d.exists() || !d.isDirectory()) return; for (File f : d.listFiles()) { if (f.isFile() && f.getName().endsWith(".nebula")) { if (log.isTraceEnabled()) { log.trace("load : " + f.toURI().toURL()); } URL url = f.toURI().toURL(); if (!loadedTypes.containsKey(url)) { this.defineNebula(f.toURI().toURL()); } } else if (f.isDirectory()) { doLoadFolder(root, f); } } } catch (Exception e) { throw new RuntimeException(e); } } @Override List<TypeImp> defineNebula(Reader in) throws RecognitionException { List<TypeImp> typeList = super.defineNebula(in); for (Type t : typeList) { TypeImp ti = (TypeImp)t; ti.mutable = true; } return typeList; } @Override List<TypeImp> defineNebula(URL in) throws IOException, RecognitionException { List<TypeImp> typeList = super.defineNebula(in); for (TypeImp t : typeList) { t.mutable = true; } loadedTypes.put(in, typeList.get(0)); return typeList; } @Override public Type update(Type oldType, String newCode) { try { List<TypeImp> newTypes = tryDefineNebula(new StringReader(newCode)); File file; if (oldType == null) { String name = newTypes.get(0).getName(); file = new File(paths.get(0), name + ".nebula"); } else { assert oldType.getName().equals(newTypes.get(0).getName()); oldType = this.findType(oldType.getName()); if (!oldType.isMutable()) { throw new RuntimeException("Cannot edit"); } unlink(oldType); if (oldType.getTypeLoader() != this) { return oldType.getTypeLoader().update(oldType, newCode); } URL url = oldType.getUrl(); file = new File(url.getFile()); if (!"file".equals(url.getProtocol())) { throw new RuntimeException("Cannot edit"); } } file = FileUtil.saveTo(newCode, file); newTypes = super.defineNebula(file.toURI().toURL()); for (Type t : newTypes) { TypeImp ti = (TypeImp)t; ti.mutable = true; } return newTypes.get(0); } catch (MalformedURLException e) { throw new RuntimeException(e); } catch (RecognitionException e) { log.error(e.getClass().getName(), e); throw new RuntimeException(e); } catch (IOException e) { log.error(e.getClass().getName(), e); throw new RuntimeException(e); } } protected void unlink(Type topType) { for (Field f : topType.getFields()) { f.type.getReferences().remove(f); if (f.attrs.containsKey(Type.ATTACH)) { f.type.getAttachedBy().remove(topType); } } for (Type oldType : topType.getSubTypes()) { for (Field f : oldType.getFields()) { f.type.getReferences().remove(f); if (f.attrs.containsKey(Type.ATTACH)) { f.type.getAttachedBy().remove(oldType); } } } } @Override protected URL loadClassData(String name) { try { File file = null; for (File path : this.paths) { file = new File(path, name.replace('.', '/') + ".nebula"); if (file.exists()) { break; } } if (file != null) { return file.toURI().toURL(); } else { return null; } } catch (MalformedURLException e) { throw new RuntimeException(e); } } } interface Tracable { Object getSource(); }