/******************************************************************************* * Copyright (c) 2006-2015 * Software Technology Group, Dresden University of Technology * DevBoost GmbH, Dresden, Amtsgericht Dresden, HRB 34001 * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * Software Technology Group - TU Dresden, Germany; * DevBoost GmbH - Dresden, Germany * - initial API and implementation ******************************************************************************/ package org.emftext.language.java; import java.io.File; import java.io.IOException; import java.net.URL; import java.util.Enumeration; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IConfigurationElement; import org.eclipse.core.runtime.IExtensionRegistry; import org.eclipse.core.runtime.ILog; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Status; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.common.util.UniqueEList; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.InternalEObject; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.resource.URIConverter; import org.eclipse.emf.ecore.util.EcoreUtil; import org.emftext.language.java.classifiers.Class; import org.emftext.language.java.classifiers.ClassifiersFactory; import org.emftext.language.java.classifiers.ConcreteClassifier; import org.emftext.language.java.containers.CompilationUnit; import org.emftext.language.java.members.Member; import org.emftext.language.java.members.MemberContainer; /** * This class is responsible for managing and retrieving Java resources to establish inter-model references between * different Java classes represented as EMF-models. */ public class JavaClasspath extends AdapterImpl { /** * Initializers can be registered to initialize each newly created classpath. An initializer may be used to connect * JaMoPP with other Java tooling (e.g., the Eclipse JDT IDE) by reading the classpath from the other tool. * <p> * Initializers can be registered at the classpath via {@link JavaClasspath#getInitializers()}. Inside Eclipse, the * extension point <i>org.emftext.language.java.java_classpath_initializer</i> may also be used for this. */ public static interface Initializer { /** * Initializes the classpath. It is called as soon as the first resource of the resource set with which the * classpath is associated accesses the classpath. * * @param resource * One resource of the associated resource set that gives context for initializing the classpath * (e.g., the URI of the resource can be analyzed). */ void initialize(Resource resource); /** * @return Should be <code>true</code>, if the classpath depends on the resource that is passed to the * {@link Initializer#initialize(Resource)} method. If one of the registered initializers returns * <code>true</code>, it enforces the usage an individual classpath for each resource set. The * {@link JavaClasspath#OPTION_USE_LOCAL_CLASSPATH} option can still be used to override this. */ boolean requiresLocalClasspath(); /** * @return <code>false</code>, if the standard lib is provided by the initializer itself and should therefore * not be registered based on the currently running JVM. If only one of the registered initializers * returns <code>false</code>, the JVM's standard lib is not registered. The * {@link JavaClasspath#OPTION_REGISTER_STD_LIB} option can still be used to override this. */ boolean requiresStdLib(); } private static class InitializerExtensionPointReader { private static void read() { if (!Platform.isRunning()) { return; } IExtensionRegistry extensionRegistry = Platform.getExtensionRegistry(); IConfigurationElement configurationElements[] = extensionRegistry .getConfigurationElementsFor(EP_JAVA_CLASSPATH_INITIALIZER); for (IConfigurationElement element : configurationElements) { try { String type = element.getAttribute("class"); if (type == null) { continue; } Initializer initializer = (Initializer) element.createExecutableExtension("class"); initializers.add(initializer); } catch (CoreException ce) { String contributingPluginID = element.getDeclaringExtension().getContributor().getName(); ILog log = Platform.getLog(Platform.getBundle(contributingPluginID)); IStatus status = new Status(IStatus.ERROR, contributingPluginID, 0, "Error instantiating Java classpath initializer", ce); log.log(status); } } } } public static final String EP_JAVA_CLASSPATH_INITIALIZER = "org.emftext.language.java.java_classpath_initializer"; private static Set<Initializer> initializers = null; public static Set<Initializer> getInitializers() { if (initializers == null) { initializers = new LinkedHashSet<Initializer>(); readInitializersExtensionPoint(); } return initializers; } private static void readInitializersExtensionPoint() { try { java.lang.Class.forName("org.eclipse.core.runtime.Platform"); } catch (ClassNotFoundException e) { // running outside Eclipse return; } InitializerExtensionPointReader.read(); } private static void initialize(Resource resource) { for (Initializer initializer : getInitializers()) { initializer.initialize(resource); } } /** * If this option is set to <code>true</code> in a resource set, each classifier loaded is registered in the URI map * of the resource set's <code>URIConverter</code>. * <p> * If the option is set to <code>false</code> (default) or not set at all, each classifier loaded is registered in * the global <code>URIConverter.URI_MAP</code>. */ public static final String OPTION_USE_LOCAL_CLASSPATH = "OPTION_USE_LOCAL_CLASSPATH"; /** * If this option is set to <code>true</code> (default) in a resource set, the Java standard library (i.e., rt.jar * or classes.jar) is registered automatically based on <code>System.getProperty("sun.boot.class.path")</code>. */ public static final String OPTION_REGISTER_STD_LIB = "OPTION_REGISTER_STD_LIB"; /** * If this option is set to <code>true</code> in a resource set, all names in a Java resource will be printed as * full-qualified names when the resource is saved. If this option is used, imports do not need to be updated when * Java resources are modified. This option is set to <code>false</code> by default. */ public static final String OPTION_ALWAYS_USE_FULLY_QUALIFIED_NAMES = "OPTION_ALWAYS_USE_FULLY_QUALIFIED_NAMES"; private boolean initialized = false; /** * Singleton instance. */ private static JavaClasspath globalClasspath = null; private JavaClasspath(URIConverter uriConverter) { this.uriConverter = uriConverter; } private JavaClasspath(Resource resource) { ResourceSet resourceSet = resource.getResourceSet(); this.uriConverter = resourceSet.getURIConverter(); } public static JavaClasspath get() { getInitializers(); if (globalClasspath == null) { globalClasspath = new JavaClasspath(URIConverter.INSTANCE); if (registerStdLibDefault()) { globalClasspath.registerStdLib(); } } return globalClasspath; } public static JavaClasspath get(EObject objectContext) { getInitializers(); if (objectContext == null) { return get(); } else { return get(objectContext.eResource()); } } public static JavaClasspath get(Resource resource) { getInitializers(); if (resource == null) { return get(); } else { ResourceSet resourceSet = resource.getResourceSet(); JavaClasspath resourceSetClasspath = get(resourceSet); if (!resourceSetClasspath.initialized) { // Set to true before calling initializers, since the initializers most likely call this method again to // obtain the class path resourceSetClasspath.initialized = true; initialize(resource); } return resourceSetClasspath; } } /** * Returns the class path for the given {@link ResourceSet}. Make sure to set the load options * {@link #OPTION_USE_LOCAL_CLASSPATH} and {@link #OPTION_REGISTER_STD_LIB} to control whether a global class path * is used for the resource set and whether the standard library shall be registered. */ public static JavaClasspath get(ResourceSet resourceSet) { return get(resourceSet, getInitializers()); } public static JavaClasspath get(ResourceSet resourceSet, Set<Initializer> initalizers) { if (resourceSet == null) { return get(); } boolean useLocalClasspath = useLocalClasspath(resourceSet, initalizers); if (useLocalClasspath) { for (Adapter adapter : resourceSet.eAdapters()) { if (adapter instanceof JavaClasspath) { JavaClasspath javaClasspath = (JavaClasspath) adapter; URIConverter newURIConverter = resourceSet.getURIConverter(); URIConverter javaClasspathUriConverter = javaClasspath.uriConverter; if (javaClasspathUriConverter != newURIConverter) { // The URI converter has been replaced, the URI map needs to be transferred. Map<URI, URI> oldUriMap = javaClasspathUriConverter.getURIMap(); Map<URI, URI> newUriMap = newURIConverter.getURIMap(); for (Entry<URI, URI> oldEntry : oldUriMap.entrySet()) { URI oldKey = oldEntry.getKey(); String oldKeyString = oldKey.toString(); if (oldKeyString.startsWith(JavaUniquePathConstructor.JAVA_CLASSIFIER_PATHMAP)) { URI oldValue = oldEntry.getValue(); newUriMap.put(oldKey, oldValue); } } javaClasspath.uriConverter = newURIConverter; } return javaClasspath; } } // If no class path was found, a new one (local class path) is created ... JavaClasspath newClasspath = new JavaClasspath(resourceSet.getURIConverter()); // ... and attached to the resource set resourceSet.eAdapters().add(newClasspath); boolean registerStdLib = registerStdLib(resourceSet, initalizers); if (registerStdLib) { newClasspath.registerStdLib(); } return newClasspath; } return get(); } private static boolean registerStdLib(ResourceSet resourceSet, Set<Initializer> initalizers) { Object registerStdLibOption = resourceSet.getLoadOptions().get(OPTION_REGISTER_STD_LIB); if (registerStdLibOption == null) { registerStdLibOption = Boolean.valueOf(registerStdLibDefault(initalizers)); } boolean registerStdLib = Boolean.TRUE.equals(registerStdLibOption); return registerStdLib; } private static boolean useLocalClasspath(ResourceSet resourceSet, Set<Initializer> initalizers) { Object localClasspathOption = resourceSet.getLoadOptions().get(OPTION_USE_LOCAL_CLASSPATH); if (localClasspathOption == null) { localClasspathOption = Boolean.valueOf(useLocalClasspathDefault(initalizers)); } boolean useLocalClasspath = Boolean.TRUE.equals(localClasspathOption); return useLocalClasspath; } public static void reset() { globalClasspath = null; } protected static boolean useLocalClasspathDefault() { return useLocalClasspathDefault(getInitializers()); } protected static boolean useLocalClasspathDefault(Set<Initializer> initializers) { boolean useLocalClasspathDefault = false; for (Initializer initializer : initializers) { // if one initializer requires a local classpath, a local classpath is used by default useLocalClasspathDefault = useLocalClasspathDefault || initializer.requiresLocalClasspath(); } return useLocalClasspathDefault; } protected static boolean registerStdLibDefault() { return registerStdLibDefault(getInitializers()); } protected static boolean registerStdLibDefault(Set<Initializer> initializers) { boolean registerStdLibDefault = true; for (Initializer initializer : initializers) { // if one initializer does not require the std. lib, we assume it provides one registerStdLibDefault = registerStdLibDefault && initializer.requiresStdLib(); } return registerStdLibDefault; } // FIXME It might be better to use a set of strings here instead of a list of string to increase lookup speed protected Map<String, List<String>> packageClassifierMap = new LinkedHashMap<String, List<String>>(); protected void registerPackage(String packageName, String className) { List<String> classesInPackage = packageClassifierMap.get(packageName); if (classesInPackage == null) { classesInPackage = new UniqueEList<String>(); packageClassifierMap.put(packageName, classesInPackage); } if (!classesInPackage.contains(className)) { classesInPackage.add(className); } } protected void unRegisterPackage(String packageName, String className) { if (packageClassifierMap.containsKey(packageName)) { packageClassifierMap.get(packageName).remove(className); } } protected List<String> getPackageContents(String packageName) { List<String> content = new UniqueEList<String>(); if (packageClassifierMap.containsKey(packageName)) { content.addAll(packageClassifierMap.get(packageName)); } // Delegate to global map Map<String, List<String>> globalPackageClassifierMap = get().packageClassifierMap; if (globalPackageClassifierMap.containsKey(packageName)) { content.addAll(globalPackageClassifierMap.get(packageName)); } return content; } public boolean existsPackage(String packageName) { if (packageClassifierMap.containsKey(packageName)) { return true; } // Delegate to global map Map<String, List<String>> globalPackageClassifierMap = get().packageClassifierMap; return globalPackageClassifierMap.containsKey(packageName); } protected URIConverter uriConverter = null; public Map<URI, URI> getURIMap() { if (uriConverter == URIConverter.INSTANCE) { return URIConverter.URI_MAP; } return uriConverter.getURIMap(); } /** * Registers all classes of the Java standard library (<code>classes.jar</code> or <code>rt.jar</code>) located at * <code>System.getProperty("sun.boot.class.path")</code>. */ public void registerStdLib() { String classpath = System.getProperty("sun.boot.class.path"); String[] classpathEntries = classpath.split(File.pathSeparator); String classesJarSuffix = File.separator + "classes.jar"; String rtJarSuffix = File.separator + "rt.jar"; for (int idx = 0; idx < classpathEntries.length; idx++) { String classpathEntry = classpathEntries[idx]; if (classpathEntry.endsWith(classesJarSuffix) || classpathEntry.endsWith(rtJarSuffix)) { URI uri = URI.createFileURI(classpathEntry); registerClassifierJar(uri); } } } /** * Registers all class files contained in the jar file located at the given URI. * * @param jarURI * the URI of JAR file to get class files from */ public void registerClassifierJar(URI jarURI) { registerClassifierJar(jarURI, ""); } public void registerClassifierJar(URI jarURI, String prefix) { ZipFile zipFile = null; try { zipFile = new ZipFile(jarURI.toFileString()); } catch (IOException e) { System.out.println("Error in opening zip file: " + jarURI.toFileString()); return; } Enumeration<? extends ZipEntry> entries = zipFile.entries(); while (entries.hasMoreElements()) { ZipEntry entry = entries.nextElement(); String entryName = entry.getName(); if (entryName.endsWith(JavaUniquePathConstructor.JAVA_CLASS_FILE_EXTENSION) && entryName.startsWith(prefix)) { String uri = "archive:" + jarURI.toString() + "!/" + entryName; String fullName = entryName.substring(prefix.length()); fullName = fullName.replace("/", "."); String packageName = ""; String className = ""; int idx = fullName.lastIndexOf("."); idx = fullName.substring(0, idx).lastIndexOf("."); if (idx >= 0) { packageName = fullName.substring(0, idx); className = fullName.substring(idx + 1, fullName.lastIndexOf(".")); } else { className = fullName.substring(0, fullName.lastIndexOf(".")); } registerClassifier(packageName, className, URI.createURI(uri)); } } } public void registerSourceOrClassFileFolder(URI folderURI) { if (!folderURI.isFile()) { return; } File sourceFolder = new File(folderURI.toFileString()); if (sourceFolder.exists()) { internalRegisterSourceOrClassFileFolder(sourceFolder, ""); } } private void internalRegisterSourceOrClassFileFolder(File folder, String packageName) { for (File child : folder.listFiles()) { if (!child.getName().startsWith(".")) { // no hidden files if (child.isDirectory()) { internalRegisterSourceOrClassFileFolder(child, packageName + child.getName() + JavaUniquePathConstructor.PACKAGE_SEPARATOR); } else { if (child.getName().endsWith(JavaUniquePathConstructor.JAVA_FILE_EXTENSION) || child.getName().endsWith(JavaUniquePathConstructor.JAVA_CLASS_FILE_EXTENSION)) { String classifierName = child.getName().substring(0, child.getName().lastIndexOf('.')); URI uri = URI.createFileURI(child.getAbsolutePath()); registerClassifier(packageName, classifierName, uri); } } } } } /** * Registers all classes defined in the given compilation unit. * * @param compilationUnit */ public void registerClassifierSource(CompilationUnit compilationUnit, URI uri) { String packageName = JavaUniquePathConstructor.packageName(compilationUnit); for (ConcreteClassifier classifier : compilationUnit.getClassifiers()) { registerClassifier(packageName, classifier.getName(), uri); registerInnerClassifiers(classifier, packageName, classifier.getName(), uri); } } /** * Registers the classifier with the given name and package that is physically located at the given URI. If there is * already a classifier registered for the given class name, the old one is replaced with the new one. * * @param packageName * the name of the package that contains the classifier * @param classifierName * the simple name of the classifier * @param physicalURI * the URI where the classifier can be found (class or source file) */ public void registerClassifier(String packageName, String classifierName, URI physicalURI) { if (classifierName == null || physicalURI == null) { return; } if (!packageName.endsWith(".") && !packageName.endsWith("$")) { packageName = packageName + "."; } String innerName = classifierName; String outerName = ""; String qualifiedName = packageName; int idx = classifierName.lastIndexOf(JavaUniquePathConstructor.CLASSIFIER_SEPARATOR); if (idx >= 0) { // The classifier name contains a "$" int indexPlusOne = idx + 1; innerName = classifierName.substring(indexPlusOne); outerName = classifierName.substring(0, indexPlusOne); if (".".equals(packageName)) { qualifiedName = outerName; } else { qualifiedName = packageName + outerName; } } synchronized (this) { registerPackage(qualifiedName, innerName); final String qualifiedClassifierName; if (".".equals(packageName)) { qualifiedClassifierName = classifierName; } else { qualifiedClassifierName = packageName + classifierName; } URI logicalURI = JavaUniquePathConstructor.getJavaFileResourceURI(qualifiedClassifierName); URI existingMapping = getURIMap().get(logicalURI); if (existingMapping != null && !physicalURI.equals(existingMapping)) { // Do nothing: Silently replace old with new version. } getURIMap().put(logicalURI, physicalURI); String outerPackage = qualifiedName; while (outerPackage.endsWith("$")) { // Make sure outer classes are registered; // This is required when class names contain $ symbols. outerPackage = outerPackage.substring(0, outerPackage.length() - 1); idx = outerPackage.lastIndexOf("$"); if (idx == -1) { idx = outerPackage.lastIndexOf("."); } int indexPlusOne = idx + 1; String outerClassifier = outerPackage.substring(indexPlusOne); outerPackage = outerPackage.substring(0, indexPlusOne); if ("".equals(outerPackage)) { outerPackage = "."; } registerPackage(outerPackage, outerClassifier); } } } private void registerInnerClassifiers(ConcreteClassifier classifier, String packageName, String className, URI uri) { for (Member innerCand : ((MemberContainer) classifier).getMembers()) { if (innerCand instanceof ConcreteClassifier) { String newClassName = className + JavaUniquePathConstructor.CLASSIFIER_SEPARATOR + innerCand.getName(); registerClassifier(packageName, newClassName, uri); registerInnerClassifiers((ConcreteClassifier) innerCand, packageName, newClassName, uri); } } } /** * Removes the classifier identified by its package and name from the class path. * * @param packageName * @param classifierName */ public void unRegisterClassifier(String packageName, String classifierName) { if (classifierName == null || classifierName.equals("")) { return; } if (!packageName.endsWith(".")) { packageName = packageName + "."; } String innerName = classifierName; String outerName = ""; String qualifiedName = packageName; int idx = classifierName.lastIndexOf(JavaUniquePathConstructor.CLASSIFIER_SEPARATOR); if (idx >= 0) { innerName = classifierName.substring(idx + 1); outerName = classifierName.substring(0, idx + 1); if (".".equals(packageName)) { qualifiedName = outerName; } else { qualifiedName = packageName + outerName; } } synchronized (this) { unRegisterPackage(qualifiedName, innerName); String fullName = null; if (".".equals(packageName)) { fullName = classifierName; } else { fullName = packageName + classifierName; } URI logicalUri = JavaUniquePathConstructor.getJavaFileResourceURI(fullName); getURIMap().remove(logicalUri); } } public boolean isRegistered(String fullQualifiedName) { int idx = fullQualifiedName.lastIndexOf(JavaUniquePathConstructor.CLASSIFIER_SEPARATOR); if (idx == -1) { idx = fullQualifiedName.lastIndexOf(JavaUniquePathConstructor.PACKAGE_SEPARATOR); } if (idx == -1) { idx = -1; } String containerName = fullQualifiedName.substring(0, idx + 1); String classifierName = fullQualifiedName.substring(idx + 1); List<String> containerContent = getPackageContents(containerName); if (containerContent == null) { return false; } return containerContent.contains(classifierName); } // This method is only for testing purpose! public Map<String, List<String>> getPackageClassifierMap() { return packageClassifierMap; } /** * Constructs a proxy pointing at the classifier identified by its fully qualified name. * * @param fullQualifiedName * a qualified class name the proxy shall point to * @return proxy element */ public EObject getClassifier(String fullQualifiedName) { InternalEObject classifierProxy = (InternalEObject) ClassifiersFactory.eINSTANCE.createClass(); URI proxyURI = JavaUniquePathConstructor.getClassifierURI(fullQualifiedName); classifierProxy.eSetProxyURI(proxyURI); // set also the name to reason about it without resolving the proxy ((Class) classifierProxy).setName(JavaUniquePathConstructor.getSimpleClassName(fullQualifiedName)); return classifierProxy; } /** * Constructs a list of proxies that point at all classifiers of the given package present in the class path. * <p> * Each proxy will have the <code>name</code> attribute set correctly such that name comparison can be done without * resolving the proxy. * * @param packageName * @return list of proxies */ public EList<EObject> getClassifiers(String packageName, String classifierQuery) { int idx = classifierQuery.lastIndexOf("$"); if (idx >= 0) { packageName = packageName + classifierQuery.substring(0, idx + 1); classifierQuery = classifierQuery.substring(idx + 1); } if (!packageName.endsWith(JavaUniquePathConstructor.PACKAGE_SEPARATOR) && !packageName.endsWith(JavaUniquePathConstructor.CLASSIFIER_SEPARATOR)) { packageName = packageName + JavaUniquePathConstructor.PACKAGE_SEPARATOR; } EList<EObject> resultList = new UniqueEList<EObject>(); synchronized (this) { for (String classifierName : getPackageContents(packageName)) { if (classifierQuery.equals("*") || classifierQuery.equals(classifierName)) { InternalEObject classifierProxy = (InternalEObject) ClassifiersFactory.eINSTANCE.createClass(); String fullQualifiedName = null; if ("".equals(packageName) || ".".equals(packageName)) { fullQualifiedName = classifierName; } else { fullQualifiedName = packageName + classifierName; } classifierProxy.eSetProxyURI(JavaUniquePathConstructor.getClassifierURI(fullQualifiedName)); // set also the name to reason about it without resolving the proxy ((Class) classifierProxy).setName(JavaUniquePathConstructor.getSimpleClassName(fullQualifiedName)); resultList.add(classifierProxy); } } } return resultList; } private EList<EObject> javaLangPackage = null; /** * Returns a list of proxies for all classes <code>java.lang.*</code>. * * @return list of proxies */ public EList<EObject> getDefaultImports() { EList<EObject> resultList = new UniqueEList<EObject>(); // java.lang package if (javaLangPackage == null) { javaLangPackage = new UniqueEList<EObject>(); javaLangPackage.addAll(getClassifiers("java.lang.", "*")); } resultList.addAll(javaLangPackage); return resultList; } public void registerClassifier(java.lang.Class<?> clazz) { URL resource = clazz.getResource(clazz.getSimpleName() + ".class"); URI classURI = URI.createFileURI(resource.getFile()); String packageName = clazz.getPackage().getName(); String classifierName = clazz.getSimpleName(); registerClassifier(packageName, classifierName, classURI); } // This cache holds all class from the <code>java.lang</code> package. It // maps their simple names to the respective Class objects. // private Map<String, Class> javaLangClassCache = new LinkedHashMap<String, Class>(); public Class getJavaLangClass(EObject commentable, String simpleName) { // This was supposed to optimize performance by reusing Class objects for types from the 'java.lang' package, // but it turned out to cause trouble because JaMoPP uses equals() to compare these Class objects at many // places. For some unknown reason, these comparisons were expecting different (i.e., non-reused( Class objects. // // Turning the cache on led to test failures, which is why we do not use it currently. Once it is clarified why // equals() is used and why reusing the Class objects causes trouble, the cache can be turned on again. // if (!javaLangClassCache.containsKey(simpleName)) { String qualifiedName = "java.lang." + simpleName; Class classifier = (Class) getClassifier(qualifiedName); EObject resolved = (ConcreteClassifier) EcoreUtil.resolve(classifier, commentable); Class returnValue = null; if (resolved instanceof Class) { returnValue = (Class) resolved; } // javaLangClassCache.put(simpleName, returnValue); return returnValue; // } // return javaLangClassCache.get(simpleName); } }