Forked from digulla/FastXtextResourceSetProvider.java
Created
December 1, 2012 21:04
-
-
Save tolland/4185108 to your computer and use it in GitHub Desktop.
Faster implementation of XtextResourceSetProvider.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.apache.log4j.Logger; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.emf.ecore.resource.ResourceSet; | |
import org.eclipse.jdt.core.IJavaProject; | |
import org.eclipse.jdt.core.JavaCore; | |
import org.eclipse.xtext.resource.XtextResourceSet; | |
import org.eclipse.xtext.ui.resource.IResourceSetProvider; | |
import org.eclipse.xtext.ui.resource.XtextResourceSetProvider; | |
import org.eclipse.xtext.ui.util.JdtClasspathUriResolver; | |
import com.google.inject.Inject; | |
import com.google.inject.Provider; | |
/** | |
* @author Aaron Digulla - Initial contribution | |
*/ | |
public class FastXtextResourceSetProvider implements IResourceSetProvider { | |
private final static Logger LOG = Logger.getLogger(FastXtextResourceSetProvider.class); | |
@Inject | |
private Provider<XtextResourceSet> resourceSetProvider; | |
@Inject | |
private PlatformURIMapCache platformURIMapCache; | |
@Inject | |
private XtextResourceSetProvider slowProvider; | |
private boolean slow = Boolean.getBoolean( "slowXtextResourceSetProvider" ); | |
public ResourceSet get(IProject project) { | |
long start = System.nanoTime(); | |
try { | |
XtextResourceSet result; | |
if( slow ) { | |
result = (XtextResourceSet) slowProvider.get( project ); | |
} else { | |
result = resourceSetProvider.get(); | |
IJavaProject javaProject = JavaCore.create(project); | |
if (javaProject != null && javaProject.exists()) { | |
result.getURIConverter().getURIMap().putAll(platformURIMapCache.computePlatformURIMap(javaProject)); | |
result.setClasspathURIContext(javaProject); | |
result.setClasspathUriResolver(new JdtClasspathUriResolver()); | |
} | |
} | |
LOG.debug( "FastXtextResourceSetProvider: map size: " + result.getURIConverter().getURIMap().size() ); | |
return result; | |
} finally { | |
long end = System.nanoTime(); | |
LOG.debug( "FastXtextResourceSetProvider: " + slow + " " + (end-start)/1000 + " us" ); | |
LOG.debug(platformURIMapCache.getStats().toString()); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* add this to the UI module: */ | |
// With this binding, Xtext editors open much faster | |
@Override | |
public Class<? extends IResourceSetProvider> bindIResourceSetProvider() { | |
return FastXtextResourceSetProvider.class; | |
} | |
@org.eclipse.xtext.service.SingletonBinding(eager=false) | |
public Class<? extends PlatformURIMapCache> bindPlatformURIMapCache() { | |
return PlatformURIMapCache.class; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import static com.google.common.collect.Maps.newHashMap; | |
import java.io.File; | |
import java.io.IOException; | |
import java.io.InputStream; | |
import java.util.Collections; | |
import java.util.HashMap; | |
import java.util.HashSet; | |
import java.util.Iterator; | |
import java.util.Map; | |
import java.util.jar.JarFile; | |
import java.util.jar.Manifest; | |
import java.util.regex.Matcher; | |
import java.util.regex.Pattern; | |
import javax.xml.parsers.SAXParser; | |
import javax.xml.parsers.SAXParserFactory; | |
import org.apache.log4j.Logger; | |
import org.eclipse.core.resources.IFile; | |
import org.eclipse.core.resources.IProject; | |
import org.eclipse.core.resources.IWorkspaceRoot; | |
import org.eclipse.core.runtime.IPath; | |
import org.eclipse.emf.common.util.URI; | |
import org.eclipse.emf.ecore.plugin.EcorePlugin; | |
import org.eclipse.jdt.core.IClasspathEntry; | |
import org.eclipse.jdt.core.IJavaProject; | |
import org.eclipse.jdt.core.JavaModelException; | |
import org.xml.sax.Attributes; | |
import org.xml.sax.InputSource; | |
import org.xml.sax.SAXException; | |
import org.xml.sax.helpers.DefaultHandler; | |
import com.google.common.cache.CacheStats; | |
/** | |
* @author Aaron Digulla - Initial contribution and API | |
*/ | |
public class PlatformURIMapCache { | |
private final static Logger LOG = Logger.getLogger(PlatformURIMapCache.class); | |
private Map<CacheKey, Map<URI, URI>> cache = newHashMap(); | |
private long hitCount; | |
private long missCount; | |
private long evictCount; | |
public PlatformURIMapCache() { | |
LOG.debug("PlatformURIMapCache new instance"); | |
} | |
public Map<URI, URI> computePlatformURIMap(IJavaProject javaProject) { | |
HashMap<URI, URI> hashMap = newHashMap(computePlatformURIMap()); | |
try { | |
if (!javaProject.exists()) | |
return hashMap; | |
IClasspathEntry[] classpath = javaProject.getResolvedClasspath(true); | |
for (IClasspathEntry classPathEntry : classpath) { | |
processClasspathEntry( hashMap, classPathEntry ); | |
} | |
} catch (JavaModelException e) { | |
LOG.error(e.getMessage(), e); | |
} | |
return hashMap; | |
} | |
private boolean slow = !Boolean.getBoolean( "cacheComputePlatformURIMap" ); | |
private Map<URI, URI> computePlatformURIMap() { | |
if( slow ) { | |
return EcorePlugin.computePlatformURIMap(); | |
} | |
HashMap<URI, URI> result = newHashMap(); | |
result.putAll(computePlatformPluginToPlatformResourceMap()); | |
result.putAll(EcorePlugin.computePlatformResourceToPlatformPluginMap(new HashSet<URI>(EcorePlugin.getEPackageNsURIToGenModelLocationMap().values()))); | |
return EcorePlugin.computePlatformURIMap(); | |
} | |
private static Pattern bundleSymbolNamePattern = Pattern.compile( "^\\s*Bundle-SymbolicName\\s*:\\s*([^\\s;]*)\\s*(;.*)?$", Pattern.MULTILINE ); | |
private Map<? extends URI, ? extends URI> computePlatformPluginToPlatformResourceMap() { | |
IWorkspaceRoot root = EcorePlugin.getWorkspaceRoot(); | |
if( null == root ) { | |
return Collections.emptyMap(); | |
} | |
IProject[] projects = root.getProjects(); | |
if( null == projects ) { | |
return Collections.emptyMap(); | |
} | |
Map<URI, URI> result = new HashMap<URI, URI>(); | |
Handler handler = new Handler(); | |
for( int i = 0, size = projects.length; i < size; ++i ) | |
{ | |
IProject project = projects[ i ]; | |
result.putAll( handler.processProject( project ) ); | |
} | |
return result; | |
} | |
class Handler extends DefaultHandler | |
{ | |
public String pluginID; | |
private boolean createParser = true; | |
private SAXParser parser = null; | |
public SAXParser getParser() { | |
if( createParser ) { | |
createParser = false; | |
SAXParserFactory parserFactory = SAXParserFactory.newInstance(); | |
parserFactory.setNamespaceAware( true ); | |
try | |
{ | |
parser = parserFactory.newSAXParser(); | |
} catch( Exception exception ) | |
{ | |
LOG.error( exception ); | |
} | |
} | |
return parser; | |
} | |
private Map<URI, URI> result; | |
private IFile manifest; | |
private IFile plugin; | |
private CacheKey key; | |
public Map<URI, URI> processProject( IProject project ) { | |
manifest = project.getFile( "META-INF/MANIFEST.MF" ); | |
plugin = project.getFile( "plugin.xml" ); | |
result = null; | |
getPluginID( project ); | |
if( null == result ) { | |
result = Collections.emptyMap(); | |
} | |
return result; | |
} | |
public String getPluginID( IProject project ) { | |
if( ! project.isOpen() ) { | |
return null; | |
} | |
pluginID = null; | |
if( manifest.exists() ) { | |
readManifest(); | |
} else if( plugin.exists() ) { | |
readPluginXml( plugin ); | |
} | |
if( result == null ) | |
{ | |
if( null == pluginID ) { | |
return null; | |
} | |
result = newHashMap(); | |
URI platformPluginURI = URI.createPlatformPluginURI( pluginID + "/", false ); | |
URI platformResourceURI = URI.createPlatformResourceURI( project.getName() + "/", true ); | |
result.put( platformPluginURI, platformResourceURI ); | |
cache.put( key, result ); | |
missCount ++; | |
} else { | |
hitCount ++; | |
} | |
return pluginID; | |
} | |
private void readPluginXml( final IFile plugin ) { | |
if( null == getParser() ) { | |
return; | |
} | |
key = new CacheKey( plugin ); | |
result = cache.get( key ); | |
if( null != result ) { | |
return; | |
} | |
try | |
{ | |
getParser().parse( new InputSource( plugin.getContents() ), this ); | |
} catch( Exception exception ) | |
{ | |
if( pluginID == null ) | |
{ | |
LOG.error( exception ); | |
} | |
} | |
} | |
private void readManifest() { | |
key = new CacheKey( manifest ); | |
result = cache.get( key ); | |
if( null != result ) { | |
hitCount ++; | |
return; | |
} | |
missCount ++; | |
InputStream inputStream = null; | |
try | |
{ | |
inputStream = manifest.getContents(); | |
int available = inputStream.available(); | |
if( bytes.length < available ) | |
{ | |
bytes = new byte[available]; | |
} | |
inputStream.read( bytes ); | |
String contents = new String( bytes, "UTF-8" ); | |
Matcher matcher = bundleSymbolNamePattern.matcher( contents ); | |
if( matcher.find() ) | |
{ | |
pluginID = matcher.group( 1 ); | |
} | |
} catch( Exception exception ) | |
{ | |
LOG.error( exception ); | |
} finally | |
{ | |
if( inputStream != null ) | |
{ | |
try | |
{ | |
inputStream.close(); | |
} catch( IOException exception ) | |
{ | |
LOG.error( exception ); | |
} | |
} | |
} | |
} | |
byte[] bytes = {}; | |
@Override | |
public void startElement( String uri, String localName, String qName, Attributes attributes ) throws SAXException | |
{ | |
if( "".equals( uri ) && "plugin".equals( localName ) ) | |
{ | |
pluginID = attributes.getValue( "id" ); | |
} | |
throw new SAXException( "Done" ); | |
} | |
} | |
public CacheStats getStats() { | |
return new CacheStats( hitCount, missCount, cache.size(), 0, 0, evictCount ); | |
} | |
protected void processClasspathEntry( HashMap<URI, URI> hashMap, IClasspathEntry classPathEntry ) { | |
IPath path = classPathEntry.getPath(); | |
if (null == path || ! "jar".equals(path.getFileExtension())) { | |
return; | |
} | |
try { | |
final File file = path.toFile(); | |
if (null == file || ! file.exists()) { | |
return; | |
} | |
processJarFile(hashMap, file); | |
} catch (IOException e) { | |
LOG.error(e.getMessage(), e); | |
} | |
} | |
protected void processJarFile( HashMap<URI, URI> hashMap, final File file ) throws IOException { | |
CacheKey key = new CacheKey( file ); | |
Map<URI, URI> cached = cache.get(key); | |
if (null == cached) { | |
cached = mapFromJarFile(file); | |
removeStaleEntry(file); | |
cache.put(key, cached); | |
missCount ++; | |
} else { | |
hitCount ++; | |
} | |
hashMap.putAll(cached); | |
} | |
private void removeStaleEntry(File file) { | |
String path = file.getAbsolutePath(); | |
for(Iterator<CacheKey> iter = cache.keySet().iterator(); iter.hasNext(); ) { | |
CacheKey key = iter.next(); | |
if(key.matches(path)) { | |
iter.remove(); | |
evictCount ++; | |
} | |
} | |
} | |
private Map<URI, URI> mapFromJarFile( File file ) throws IOException { | |
JarFile jarFile = new JarFile(file); | |
try { | |
Manifest manifest = jarFile.getManifest(); | |
if (null == manifest) { | |
return Collections.emptyMap(); | |
} | |
String name = manifest.getMainAttributes().getValue("Bundle-SymbolicName"); | |
if (null == name) { | |
return Collections.emptyMap(); | |
} | |
name = stripSemicolon( name ); | |
if (EcorePlugin.getPlatformResourceMap().containsKey(name)) { | |
return Collections.emptyMap(); | |
} | |
String p = "archive:" + file.toURI() + "!/"; | |
URI uri = URI.createURI(p); | |
final URI platformResourceKey = URI.createPlatformResourceURI(name + "/", false); | |
final URI platformPluginKey = URI.createPlatformPluginURI(name + "/", false); | |
Map<URI, URI> result = newHashMap(); | |
result.put(platformResourceKey, uri); | |
result.put(platformPluginKey, uri); | |
return result; | |
} finally { | |
jarFile.close(); | |
} | |
} | |
protected String stripSemicolon( String name ) { | |
final int indexOf = name.indexOf(';'); | |
if (indexOf > 0) | |
name = name.substring(0, indexOf); | |
return name; | |
} | |
private static class CacheKey { | |
private String path; | |
private long size; | |
private long lastModified; | |
public CacheKey( File file ) { | |
path = file.getAbsolutePath(); | |
size = file.length(); | |
lastModified = file.lastModified(); | |
} | |
public CacheKey( IFile file ) { | |
path = file.getLocationURI().toString(); | |
size = 0; // doesn't exist in the API :-/ | |
lastModified = file.getModificationStamp(); | |
} | |
public boolean matches( String path ) { | |
return this.path.equals(path); | |
} | |
@Override | |
public int hashCode() { | |
final int prime = 31; | |
int result = 1; | |
result = prime * result + (int) ( lastModified ^ ( lastModified >>> 32 ) ); | |
result = prime * result + (int) ( size ^ ( size >>> 32 ) ); | |
result = prime * result + ( ( path == null ) ? 0 : path.hashCode() ); | |
return result; | |
} | |
@Override | |
public boolean equals( Object obj ) { | |
if( this == obj ) { | |
return true; | |
} | |
if( obj == null ) { | |
return false; | |
} | |
if( getClass() != obj.getClass() ) { | |
return false; | |
} | |
CacheKey other = (CacheKey) obj; | |
if( lastModified != other.lastModified ) { | |
return false; | |
} | |
if( size != other.size ) { | |
return false; | |
} | |
if( path == null ) { | |
if( other.path != null ) { | |
return false; | |
} | |
} else if( !path.equals( other.path ) ) { | |
return false; | |
} | |
return true; | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment