1 package gnu.expr; 2 import java.net.*; 3 import gnu.kawa.io.Path; 4 import gnu.kawa.io.URLPath; 5 import gnu.mapping.WrappedException; 6 import gnu.bytecode.ClassType; 7 8 /** A database of known modules as represented by {@link ModuleInfo}. 9 * Currently there is only a single global instance of {@code ModuleManager}; 10 * in the future each different "application" may have their own. 11 */ 12 13 public class ModuleManager 14 { 15 private String compilationDirectory = ""; setCompilationDirectory(String path)16 public void setCompilationDirectory (String path) 17 { 18 if (path == null) 19 path = ""; 20 int plen = path.length(); 21 if (plen > 0) 22 { 23 char sep = java.io.File.separatorChar; // Or '/' if path is a URL?? 24 if (path.charAt(plen - 1) != sep) 25 path = path + sep; 26 } 27 compilationDirectory = path; 28 } getCompilationDirectory()29 public String getCompilationDirectory () { return compilationDirectory; } 30 31 static ModuleManager instance = new ModuleManager(); 32 33 /** For now assumes a single global ModuleManager. 34 * Later, might have multiple managers. */ getInstance()35 public static ModuleManager getInstance() { return instance; } 36 37 int interactiveCounter; 38 39 int evalCounter; 40 41 public static final String interactiveClassPrefix = "atInteractiveLevel-"; 42 public String evalClassPrefix = "atEvalLevel-"; 43 44 /** Used to generate unique class names for interactive REPLs and loads. 45 * This is incremented from Shell.run. 46 * Unique class names are essential for {@link Compilation#usedClass}. 47 * They're also desirable for debugging. 48 */ getNewInteractiveName()49 public synchronized String getNewInteractiveName() { 50 return interactiveClassPrefix + (++interactiveCounter); 51 } 52 53 /** Used to generate unique class names for other evals. 54 * Equivalent in functionality to getNewInteractiveName, but with 55 * a different prefix, for better user-friendliness. 56 */ getNewEvalName()57 public synchronized String getNewEvalName() { 58 return evalClassPrefix + (++evalCounter); 59 } 60 61 public static final long LAST_MODIFIED_CACHE_TIME = 1000; 62 /** Number of milliseconds before we re-check file's modified time. */ 63 public long lastModifiedCacheTime = LAST_MODIFIED_CACHE_TIME; 64 65 /** List of all modules managed by this ModuleManager. */ 66 ModuleInfo[] modules; 67 int numModules; 68 getModule(int index)69 public synchronized ModuleInfo getModule (int index) 70 { 71 return index >= numModules ? null : modules[index]; 72 } 73 find(Compilation comp)74 public synchronized ModuleInfo find (Compilation comp) 75 { 76 ModuleExp mexp = comp.getModule(); 77 ClassType ctype = mexp.classFor(comp); 78 String fileName = mexp.getFileName(); 79 Path sourceAbsPath = ModuleInfo.absPath(fileName); 80 ModuleInfo info = findWithSourcePath(sourceAbsPath, fileName); 81 info.setClassName(ctype.getName()); 82 info.setCompilation(comp); 83 return info; 84 } 85 add(ModuleInfo info)86 private synchronized void add (ModuleInfo info) 87 { 88 if (modules == null) 89 modules = new ModuleInfo[10]; 90 else if (numModules == modules.length) 91 { 92 ModuleInfo[] tmp = new ModuleInfo[2 * numModules]; 93 System.arraycopy(modules, 0, tmp, 0, numModules); 94 modules = tmp; 95 } 96 modules[numModules++] = info; 97 } 98 createWithClassName(String className)99 public ModuleInfo createWithClassName(String className) { 100 ModuleInfo info = new ModuleInfo(); 101 info.setClassName(className); 102 add(info); 103 return info; 104 } 105 searchWithClassName(String className)106 public synchronized ModuleInfo searchWithClassName (String className) 107 { 108 for (int i = numModules; --i >= 0; ) 109 { 110 ModuleInfo info = modules[i]; 111 if (className.equals(info.getClassName())) 112 return info; 113 } 114 return null; 115 } 116 findWithClass(Class clas)117 public static synchronized ModuleInfo findWithClass (Class clas) 118 { 119 ModuleInfo info = ModuleInfo.mapClassToInfo.get(clas); 120 if (info == null) 121 { 122 info = new ModuleInfo(); 123 info.setModuleClass(clas); 124 } 125 return info; 126 } 127 findWithClassName(String className)128 public ModuleInfo findWithClassName (String className) 129 { 130 ModuleInfo info = searchWithClassName(className); 131 if (info != null) 132 return info; 133 try 134 { 135 return findWithClass(ClassType.getContextClass(className)); 136 } 137 catch (Exception ex) 138 { 139 throw WrappedException.wrapIfNeeded(ex); 140 } 141 } 142 searchWithAbsSourcePath(String sourcePath)143 private synchronized ModuleInfo searchWithAbsSourcePath (String sourcePath) 144 { 145 for (int i = numModules; --i >= 0; ) 146 { 147 ModuleInfo info = modules[i]; 148 if (sourcePath.equals(info.getSourceAbsPathname())) 149 return info; 150 } 151 return null; 152 } 153 154 public synchronized ModuleInfo findWithSourcePath(Path sourceAbsPath, String sourcePath)155 findWithSourcePath (Path sourceAbsPath, String sourcePath) 156 { 157 String sourceAbsPathname = sourceAbsPath.toString(); 158 ModuleInfo info = searchWithAbsSourcePath(sourceAbsPathname); 159 if (info == null) 160 { 161 info = new ModuleInfo(); 162 info.sourcePath = sourcePath; 163 info.sourceAbsPath = sourceAbsPath; 164 info.sourceAbsPathname = sourceAbsPathname; 165 add(info); 166 } 167 return info; 168 } 169 findWithSourcePath(String sourcePath)170 public synchronized ModuleInfo findWithSourcePath (String sourcePath) 171 { 172 return findWithSourcePath(ModuleInfo.absPath(sourcePath), sourcePath); 173 } 174 findWithURL(URL url)175 public synchronized ModuleInfo findWithURL (URL url) 176 { 177 Path sourceAbsPath = URLPath.valueOf(url); 178 String sourcePath = url.toExternalForm(); 179 return findWithSourcePath(sourceAbsPath, sourcePath); 180 } 181 182 /** Called by compiler-generated code. 183 * The compiler generates in each package a class that extends 184 * {@link ModuleSet}, and that contains a 185 * {@link ModuleSet#register(ModuleManager)} method that calls 186 * back to this method. This method then registers the specified module. 187 */ register(String moduleClass, String moduleSource, String moduleUri)188 public synchronized void register (String moduleClass, String moduleSource, String moduleUri) 189 { 190 // Unclear what is the right thing to do if we have an existing module 191 // with the same source or class name. One case is when we're explicitly 192 // compiling a source file and (in XQuery) importing (other) modules in the 193 // same namespace. In that case the file we're compiling should take 194 // precedence over old data in the packages's existing $ModulesMap$ class. 195 if (searchWithClassName(moduleClass) != null) 196 return; 197 Path sourcePath = Path.valueOf(moduleSource); 198 Path sourceAbsPath = sourcePath.getCanonical(); 199 String sourceAbsPathname = sourceAbsPath.toString(); 200 if (searchWithAbsSourcePath(sourceAbsPathname) != null) 201 return; 202 ModuleInfo info = new ModuleInfo(); 203 if (sourcePath.isAbsolute()) 204 { 205 info.sourceAbsPath = sourcePath; 206 info.sourceAbsPathname = sourceAbsPathname; 207 } 208 else 209 { 210 // Resolve moduleSource against moduleClass path. 211 try 212 { 213 // See comment in loadPackageInfo. 214 Class setClass = this.packageInfoChain.getClass(); 215 String setClassName = setClass.getName().replace('.', '/')+".class"; 216 java.net.URL setClassURL 217 = setClass.getClassLoader().getResource(setClassName); 218 sourceAbsPath = URLPath.valueOf(setClassURL).resolve(moduleSource); 219 info.sourceAbsPath = sourceAbsPath; 220 info.sourceAbsPathname = sourceAbsPath.toString(); 221 } 222 catch (Exception ex) 223 { 224 return; 225 } 226 } 227 info.setClassName(moduleClass); 228 info.sourcePath = moduleSource; 229 info.uri = moduleUri; 230 add(info); 231 } 232 233 /** List of {@link ModuleSet}s registered with this {@code ModuleManager}. */ 234 ModuleSet packageInfoChain; 235 236 /** Search for and if needed load the {@link ModuleSet} for a package. 237 */ loadPackageInfo(String packageName)238 public synchronized void loadPackageInfo (String packageName) 239 throws ClassNotFoundException, InstantiationException, IllegalAccessException 240 { 241 String moduleSetClassName = packageName + "." + ModuleSet.MODULES_MAP; 242 243 for (ModuleSet set = packageInfoChain; set != null; set = set.next) 244 { 245 String setName = set.getClass().getName(); 246 if (setName.equals(moduleSetClassName)) 247 continue; 248 } 249 Class setClass = Class.forName(moduleSetClassName); 250 ModuleSet instance = (ModuleSet) setClass.newInstance(); 251 252 instance.next = this.packageInfoChain; 253 // The ModuleInfo.register method depends on packageInfoChain being 254 // the current ModuleSet. Bit of a kludge. 255 this.packageInfoChain = instance; 256 instance.register(this); 257 } 258 259 /** Reset the set of known modules. */ clear()260 public synchronized void clear () 261 { 262 // Clear modules and packageIndoChain lists. 263 // We also clean the 'next' fields, to avoid leaks if 264 // somethings happens to be pointing at a ModuleInto or ModuleSet. 265 // This may be overkill. 266 ModuleSet set = packageInfoChain; 267 while (set != null) 268 { 269 ModuleSet next = set.next; 270 set.next = null; 271 set = next; 272 } 273 packageInfoChain = null; 274 275 modules = null; 276 numModules = 0; 277 } 278 } 279