/*
* Copyright (c) 2009 Andrejs Jermakovics.
*
* 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:
* Andrejs Jermakovics - initial implementation
*/
package it.unibz.instasearch.indexing;
import it.unibz.instasearch.InstaSearchPlugin;
import it.unibz.instasearch.prefs.PreferenceConstants;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeSet;
import org.apache.lucene.index.IndexWriter;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IStorage;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJarEntryResource;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
import org.eclipse.ui.IEditorInput;
/**
* Workspace indexer which also indexes JAR source attachments
*/
@SuppressWarnings("restriction")
public class WorkspaceIndexerJDT extends WorkspaceIndexer {
/**
* @throws Exception
* @throws IOException
*/
public WorkspaceIndexerJDT() throws Exception {
super();
if( JavaCore.getJavaCore() == null ) // check that we have JDT. throws exception if we don't have JavaCore
throw new RuntimeException("JDT not detected");
}
@Override
protected void indexContainers(IndexWriter indexWriter, IWorkspaceRoot workspaceRoot, IProgressMonitor monitor) throws Exception {
super.indexContainers(indexWriter, workspaceRoot, monitor);
boolean indexArchives = InstaSearchPlugin.getBoolPref(PreferenceConstants.P_INDEX_ARCHIVES);
if( ! indexArchives )
return;
try {
IJavaModel javaModel = JavaCore.create(workspaceRoot);
List<IPackageFragmentRoot> jars = getJars(javaModel);
monitor.beginTask("Indexing JAR Source Attachements (" + jars.size() + ")", jars.size());
for(IPackageFragmentRoot jar: jars) {
monitor.worked(1);
indexClassFiles(indexWriter, jar, monitor);
indexNonJavaResources(indexWriter, jar, monitor);
if( monitor.isCanceled() )
break;
}
} catch(Exception e) {
InstaSearchPlugin.log(e);
}
if( monitor.isCanceled() ) {
// if user canceled, disable jar indexing in preferences. next time don't index
InstaSearchPlugin.setBoolPref(PreferenceConstants.P_INDEX_ARCHIVES, false);
}
monitor.done();
}
/**
* Index non .class files
*
* @param indexWriter
* @param jar
* @param monitor
* @throws CoreException
* @throws IOException
*/
private void indexNonJavaResources(IndexWriter indexWriter, IPackageFragmentRoot jar, IProgressMonitor monitor) throws Exception {
Object[] resources = jar.getNonJavaResources();
if( resources == null || resources.length == 0 )
return;
IJarEntryResource[] jarEntries = new IJarEntryResource[resources.length];
System.arraycopy(resources, 0, jarEntries, 0, resources.length);
indexNonJavaResources(indexWriter, jar, jarEntries, monitor);
}
/**
* @param indexWriter
* @param jar
* @param resources
* @param monitor
* @throws IOException
* @throws CoreException
*/
private void indexNonJavaResources(IndexWriter indexWriter, IPackageFragmentRoot jar, IJarEntryResource[] resources, IProgressMonitor monitor) throws Exception {
String jarName = getJarName(jar);
String projectPath = getProjectPath(jar);
for(IJarEntryResource resource: resources)
{
if( monitor.isCanceled() )
return;
if( resource.isFile() )
{
if( isIndexable(resource) )
indexStorageWithRetry(indexWriter, resource, projectPath, IResource.NULL_STAMP, jarName);
}
else
{
indexNonJavaResources(indexWriter, jar, resource.getChildren(), monitor);
}
}
}
/**
* @param jarRes
* @return
*/
private boolean isIndexable(IJarEntryResource jarRes) {
String ext = jarRes.getFullPath().getFileExtension();
return isIndexableExtension(ext);
}
/**
* @param jar
* @return
*/
private String getProjectPath(IPackageFragmentRoot jar) {
return jar.getJavaProject().getElementName() + "/" + getJarName(jar);
}
/**
* @param jar
* @return
*/
private String getJarName(IPackageFragmentRoot jar) {
return jar.getElementName();
}
private void indexClassFiles(IndexWriter indexWriter, IPackageFragmentRoot jar, IProgressMonitor monitor) throws Exception {
String jarName = getJarName(jar);
String projectPath = getProjectPath(jar);
for(IJavaElement pkgRootChild: jar.getChildren()) {
IPackageFragment pkg = (IPackageFragment) pkgRootChild;
monitor.setTaskName("Indexing JAR Source Attachements: " + jar.getElementName() + " - " + pkg.getElementName());
for(IClassFile classFile: pkg.getClassFiles()) {
if( classFile.getElementName().contains("$") ) continue; // not type root
try {
ClassFileSourceStorage classFileSourceStorage = new ClassFileSourceStorage(classFile);
if (classFileSourceStorage.hasSource()) indexStorageWithRetry(indexWriter, classFileSourceStorage, projectPath, IResource.NULL_STAMP, jarName);
} catch (Exception e) {
//Issue #69. Avoid Ex where Eclipse has trouble loading a source file.
}
if( monitor.isCanceled() ) return;
}
}
}
private List<IPackageFragmentRoot> getJars(IJavaModel javaModel) throws JavaModelException {
IJavaProject[] projects = javaModel.getJavaProjects();
TreeSet<String> jarNames = new TreeSet<String>();
LinkedList<IPackageFragmentRoot> jars = new LinkedList<IPackageFragmentRoot>();
for(IJavaProject javaProj: projects) {
IPackageFragmentRoot[] roots = javaProj.getPackageFragmentRoots();
for(IPackageFragmentRoot root: roots) {
if( root.isArchive() && root.getSourceAttachmentPath() != null ) {
String name = root.getElementName();
if( !jarNames.contains(name) ) {
jarNames.add(name);
jars.add(root);
}
}
}
}
return jars;
}
@Override
public IEditorInput getEditorInput(SearchResultDoc doc) throws Exception {
if( ! doc.isInJar() )
return super.getEditorInput(doc);
if( "class".equals(doc.getFileExtension()) ) {
IClassFile classFile = getClassFile(doc);
if( classFile == null )
return null;
return EditorUtility.getEditorInput(classFile);
}
IStorage storage = getNonJavaResource(doc);
if( storage == null )
return null;
return EditorUtility.getEditorInput(storage);
}
@Override
public IStorage getStorage(SearchResultDoc doc) throws Exception {
if( !doc.isInJar() )
return super.getStorage(doc); // return file
if( "class".equals(doc.getFileExtension())) {
IClassFile classFile = getClassFile(doc);
if( classFile == null )
return null;
ClassFileSourceStorage storage = new ClassFileSourceStorage(classFile);
return storage;
}
return getNonJavaResource(doc);
}
/**
* @param doc
* @return
* @throws JavaModelException
*/
private IStorage getNonJavaResource(SearchResultDoc doc) throws JavaModelException {
IWorkspaceRoot workspaceRoot = InstaSearchPlugin.getWorkspaceRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
String javaProjectName = doc.getProject().segment(0);
IJavaProject javaProj = javaModel.getJavaProject(javaProjectName);
if( !javaProj.isOpen() )
javaProj.open(new NullProgressMonitor());
javaModel.refreshExternalArchives(new IJavaElement[]{javaProj}, new NullProgressMonitor());
String jarName = doc.getJarName();
IPackageFragmentRoot[] roots = javaProj.getPackageFragmentRoots();
IPackageFragmentRoot jar = null;
for(IPackageFragmentRoot root: roots) {
if( root.isArchive() && root.getSourceAttachmentPath() != null ) {
String name = root.getElementName();
if( name.equals(jarName)) {
jar = root;
break;
}
}
}
if( jar == null )
return null;
String filePath = doc.getFilePath();
IPath path = new Path(filePath);
IJarEntryResource res = null;
for(String segment: path.segments())
{
if( res == null )
res = findJarEntry(jar, segment);
else
res = findJarEntry(res.getChildren(), segment);
}
return res;
}
/**
* @param jar
* @param filePath
* @return
* @throws JavaModelException
*/
private IJarEntryResource findJarEntry(IPackageFragmentRoot jar, String filePath) throws JavaModelException {
Object[] resources = jar.getNonJavaResources();
if( resources == null || resources.length == 0 )
return null;
IJarEntryResource[] jarEntries = new IJarEntryResource[resources.length];
System.arraycopy(resources, 0, jarEntries, 0, resources.length);
return findJarEntry(jarEntries, filePath);
}
/**
* @param jarEntries
* @param filePath
* @return
*/
private IJarEntryResource findJarEntry(IJarEntryResource[] jarEntries,
String filePath) {
for(IJarEntryResource entry: jarEntries)
{
if( filePath.equals(entry.getName()) )
return entry;
}
return null;
}
/**
* @param doc
* @return
* @throws JavaModelException
*/
private IClassFile getClassFile(SearchResultDoc doc) throws Exception {
IWorkspaceRoot workspaceRoot = InstaSearchPlugin.getWorkspaceRoot();
IJavaModel javaModel = JavaCore.create(workspaceRoot);
String javaProjectName = doc.getProject().segment(0);
IJavaProject proj = javaModel.getJavaProject(javaProjectName);
if( proj == null )
throw new Exception("Project " + javaProjectName + " not found");
if( !proj.isOpen() )
proj.open(new NullProgressMonitor());
javaModel.refreshExternalArchives(new IJavaElement[]{proj}, new NullProgressMonitor());
IPath filePath = new Path(doc.getFilePath());
String fileName = filePath.lastSegment();
IPath jarPath = filePath.removeLastSegments(2); // remove pkg and filename
IPackageFragmentRoot jar = null;
IResource jarFile = workspaceRoot.findMember(jarPath);
if( jarFile != null )
jar = proj.getPackageFragmentRoot(jarFile);
else
jar = proj.getPackageFragmentRoot(jarPath.toString()); // external archive
if( jar == null )
throw new Exception("Jar " + jarPath + " not found in project " + doc.getProjectName());
IPath pkgPath = filePath.removeLastSegments(1); // remove filename
String pkgName = pkgPath.lastSegment();
IPackageFragment pkg = jar.getPackageFragment(pkgName);
if( pkg == null )
throw new Exception("Package " + pkgName + " not found in " + doc.getProjectName());
IClassFile classFile = pkg.getClassFile(fileName);
return classFile;
}
/**
* Stores the attached source of a .class file
*/
private class ClassFileSourceStorage implements IStorage {
private IClassFile classFile;
private String source;
/**
* @param classFileWithSource
* @throws JavaModelException
*
*/
public ClassFileSourceStorage(IClassFile classFileWithSource) throws JavaModelException {
this.classFile = classFileWithSource;
this.source = classFile.getSource();
}
public InputStream getContents() throws CoreException {
if( !hasSource() ) return null;
return new ByteArrayInputStream(source.getBytes());
}
public boolean hasSource() {
return source != null;
}
/**
* <jar path>/<package name>/<file name>
*/
public IPath getFullPath() {
IPackageFragment pkg = (IPackageFragment)classFile.getParent();
IPackageFragmentRoot jar = (IPackageFragmentRoot)pkg.getParent();
String pkgName = pkg.getElementName();
IPath jarPath = jar.getPath();
IPath filePath = jarPath.append(pkgName).append(getName());
return filePath;
}
public String getName() {
return classFile.getElementName(); // ClassName.class
}
public boolean isReadOnly() {
return true;
}
@SuppressWarnings("rawtypes")
public Object getAdapter(Class adapter) {
return classFile.getAdapter(adapter);
}
}
}