blob: f7fe6c832f74a0eb9cce06738ecd54140ce0302a [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2009 Mia-Software.
* 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:
* Romain Dervaux (Mia-Software) - initial API and implementation
*******************************************************************************/
package org.eclipse.modisco.java.discoverer.internal.io.library;
import java.util.HashMap;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.gmt.modisco.infra.common.core.logging.MoDiscoLogger;
import org.eclipse.gmt.modisco.java.Archive;
import org.eclipse.gmt.modisco.java.Model;
import org.eclipse.gmt.modisco.java.emf.JavaFactory;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.modisco.java.discoverer.internal.IModelReader;
import org.eclipse.modisco.java.discoverer.internal.JavaActivator;
import org.eclipse.modisco.java.discoverer.internal.Messages;
import org.eclipse.modisco.java.discoverer.internal.io.java.JavaReader;
import org.eclipse.modisco.java.discoverer.internal.io.java.MethodRedefinitionManager;
import org.eclipse.modisco.java.discoverer.internal.io.java.binding.BindingManager;
/**
* A {@code LibraryReader} reads the contents of .class files and builds the
* corresponding Java model.
* <p>
* As source, a {@code LibraryReader} accepts {@link IPackageFragmentRoot
* libraries} and single .class {@link IClassFile files}.
* </p>
* <p>
* The analysis doesn't go beyond root types and members. Bytecode (inside
* methods), local classes & anonymous classes are ignored.
* </p>
* <p>
* The model is built along the Java 1.4 language specification. This means that
* all specificities of Java 5 are not handled (Enumerations and annotations are
* translated respectively as classes & interfaces, only raw types of
* parameterized types are considered, type variables & wildcard types are
* translated as basic {@link Object}s, ...).
* </p>
* <p>
* It handles both internal and {@link IPackageFragmentRoot#isExternal()
* external} libraries.
* </p>
* <p>
* If sources are
* {@link IPackageFragmentRoot#attachSource(IPath, IPath, org.eclipse.core.runtime.IProgressMonitor)
* attached} to the library, the use of the
* {@link LibraryReaderOptions#USE_SOURCES USE_SOURCES} option will cause the
* creation of the model from the sources, relying on a {@code JavaReader}.
* </p>
*
* @see JavaReader
*/
public class LibraryReader implements IModelReader {
/**
* the EMF factory.
*/
private final JavaFactory factory;
/**
* the resulting model.
*/
private Model resultModel;
/**
* the global {@code BindingManager}.
*/
private BindingManager globalBindings;
/**
* some options for this reader.
*/
@SuppressWarnings("unused")
private final Map<String, Object> options;
/**
* a {@code TypeFinder}.
*/
private TypeFinder typeFinder;
/**
* indicate if the user wanted to analyse the sources
*/
private final boolean useSources;
/**
* Contructs a new {@code LibraryReader} with no options.
*
* @param factory
* the EMF factory
* @param library
* the library
*/
public LibraryReader(final JavaFactory factory) {
this(factory, new HashMap<String, Object>());
}
/**
* Contructs a new {@code LibraryReader} with options.
*
* @param factory
* the EMF factory
* @param library
* the library to analyse
* @param options
* the {@link LibraryReaderOptions options} of this
* {@code LibraryReader}
*/
public LibraryReader(final JavaFactory factory, final Map<String, Object> options) {
this.factory = factory;
this.options = options;
this.useSources = Boolean.TRUE.equals(options.get(LibraryReaderOptions.USE_SOURCES
.toString()));
}
public void readModel(final Object source, final Model resultModel1,
final IProgressMonitor monitor) {
readModel(source, resultModel1, getBindingManager(), monitor);
}
public void readModel(final Object source, final Model resultModel1,
final BindingManager globalBindings1, final IProgressMonitor monitor) {
if (source == null) {
return;
}
this.resultModel = resultModel1;
this.globalBindings = globalBindings1;
ClassFileParserUtils.initializePrimitiveTypes(this.factory, resultModel1,
this.globalBindings);
try {
if (source instanceof IPackageFragmentRoot) {
IPackageFragmentRoot library = (IPackageFragmentRoot) source;
if (resultModel1.getName() == null || resultModel1.getName().length() == 0) {
resultModel1.setName(library.getElementName());
}
this.typeFinder = new TypeFinder(library.getJavaProject());
IJavaElement[] children = library.getChildren();
for (IJavaElement element : children) {
IPackageFragment packageFolder = (IPackageFragment) element;
if (packageFolder.getClassFiles().length > 0) {
// report some feedback
monitor.subTask(Messages.LibraryReader_DiscoveringTask
+ packageFolder.getElementName());
// parse package
parsePackage(library, resultModel1, packageFolder, monitor);
if (monitor.isCanceled()) {
return;
}
}
}
} else if (source instanceof IClassFile) {
IClassFile cf = (IClassFile) source;
this.typeFinder = new TypeFinder(cf.getJavaProject());
parseClassFile(cf);
} else {
throw new IllegalArgumentException(
"Library reader can not handle source object : " + source.toString()); //$NON-NLS-1$
}
} catch (Exception e) {
MoDiscoLogger.logError(e, JavaActivator.getDefault());
}
}
protected void parsePackage(final IPackageFragmentRoot library, final Model resultModel1,
final IPackageFragment parent, final IProgressMonitor monitor)
throws JavaModelException {
IClassFile[] children = parent.getClassFiles();
for (IClassFile cf : children) {
parseClassFile(cf);
if (monitor.isCanceled()) {
return;
}
}
}
protected void parseClassFile(final IClassFile classFile) {
try {
IType type = classFile.getType();
// we want only top level types
if (type != null && type.exists() && !type.isAnonymous() && !type.isLocal()
&& !type.isMember()) {
String filePath = getPath(classFile);
visitClassFile(classFile, filePath);
}
} catch (Exception e) {
MoDiscoLogger.logError(e, JavaActivator.getDefault());
}
}
protected void visitClassFile(final IClassFile classFile, final String filePath)
throws JavaModelException {
boolean classFileHasSource = false;
String fileContent = null;
// test if user wants to analyse source and if this class file has an
// attached source
if (this.useSources) {
fileContent = classFile.getSource();
if (fileContent != null) {
classFileHasSource = true;
}
}
if (classFileHasSource) {
// source code retrieved, delegate model creation to a JavaReader
IModelReader javaReader = new JavaReader(this.factory, null, null);
javaReader.readModel(classFile, this.resultModel, this.globalBindings,
new NullProgressMonitor());
} else {
// no source has been retrieved
ClassFileParser jdtVisitor = new ClassFileParser(this.factory, this.resultModel,
this.globalBindings, this.typeFinder, filePath);
jdtVisitor.parse(classFile);
}
}
protected void resolveMethodRedefinition(final Model resultModel1) {
MethodRedefinitionManager.resolveMethodRedefinitions(resultModel1, this.factory);
}
protected void finalResolveBindings(final Model resultModel1) {
this.globalBindings.resolveBindings(resultModel1);
}
protected BindingManager getBindingManager() {
BindingManager bindingManager = new BindingManager(this.factory);
return bindingManager;
}
public void terminate(final IProgressMonitor monitor) {
monitor.subTask(Messages.LibraryReader_BindingTask);
finalResolveBindings(this.resultModel);
monitor.subTask(Messages.LibraryReader_RedefinitionsTask);
resolveMethodRedefinition(this.resultModel);
}
/**
* Returns the {@link Archive} object which corresponds to the
* {@link IPackageFragmentRoot#isArchive() archive} in which this class file
* is contained. If a corresponding archive is present in the {@code model},
* it is returned, or a new one is created and added to the {@code model}.
*
* @param classFile
* the class file
* @param factory
* the EMF factory
* @param model
* the {@code Model}
* @return the {@code Archive object}, or {@code null} if {@code classFile}
* is not contained in an archive
*/
public static Archive getArchive(final IClassFile classFile, final JavaFactory factory,
final Model model) {
Archive archive = null;
IPackageFragmentRoot root = (IPackageFragmentRoot) classFile
.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
if (root != null && root.isArchive()) {
String libraryPath = getPath(root);
// class file is in a library
for (Archive itElement : model.getArchives()) {
if (itElement.getOriginalFilePath().equals(libraryPath)) {
return itElement;
}
}
// if non present in model, create a new one
archive = factory.createArchive();
archive.setName(root.getElementName());
archive.setOriginalFilePath(libraryPath);
ManifestReader.completeArchiveWithManifest(root, archive, factory);
model.getArchives().add(archive);
}
return archive;
}
/**
* Returns the archive-relative path of the class file. If this class file
* is in an archive (workspace or external), the path will be the path
* inside the archive. If it is in a folder (workspace or external), the
* path will be the full absolute path to this class file.
*
* @param classFile
* the class file
* @return the archive-relative path
*/
public static String getPath(final IClassFile classFile) {
IPackageFragmentRoot library = (IPackageFragmentRoot) classFile
.getAncestor(IJavaElement.PACKAGE_FRAGMENT_ROOT);
String filePath = null;
if (library.isArchive()) { // zip or jar
IPackageFragment parent = (IPackageFragment) classFile.getParent();
String packagePath = parent.getElementName().replace('.', '/');
filePath = '/' + packagePath + '/' + classFile.getElementName();
} else { // folder
if (library.isExternal()) {
filePath = classFile.getPath().toOSString();
} else {
IFile file = ResourcesPlugin.getWorkspace().getRoot().getFile(classFile.getPath());
filePath = file.getLocation().toOSString();
}
}
return filePath;
}
/**
* Returns the absolute path of this library in the filesystem.
*
* @param library
* the library
* @return the absolute path of this library
*/
public static String getPath(final IPackageFragmentRoot library) {
String filePath = library.getPath().toOSString();
// non external resources are relative to the workspace
if (!library.isExternal()) {
IResource resource = null;
if (library.isArchive()) { // zip or jar
resource = ResourcesPlugin.getWorkspace().getRoot().getFile(library.getPath());
} else { // folder
resource = ResourcesPlugin.getWorkspace().getRoot().getFolder(library.getPath());
}
filePath = resource.getLocation().toOSString();
}
return filePath;
}
/**
* @see org.eclipse.jdt.core.ISourceReference#getSource()
*/
public static String getFileContent(final IClassFile classFile) {
String source = null;
try {
source = classFile.getSource();
} catch (JavaModelException e) {
// Nothing
assert (true); // dummy code for "EmptyBlock" rule
}
return source;
}
}