/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.felix.sigil.eclipse.internal.model.project;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import org.apache.felix.sigil.common.config.BldFactory;
import org.apache.felix.sigil.common.config.IBldProject;
import org.apache.felix.sigil.common.config.IRepositoryConfig;
import org.apache.felix.sigil.common.model.AbstractCompoundModelElement;
import org.apache.felix.sigil.common.model.ICapabilityModelElement;
import org.apache.felix.sigil.common.model.IModelElement;
import org.apache.felix.sigil.common.model.IModelWalker;
import org.apache.felix.sigil.common.model.IRequirementModelElement;
import org.apache.felix.sigil.common.model.ModelElementFactory;
import org.apache.felix.sigil.common.model.eclipse.ISigilBundle;
import org.apache.felix.sigil.common.model.osgi.IBundleModelElement;
import org.apache.felix.sigil.common.model.osgi.IPackageExport;
import org.apache.felix.sigil.common.model.osgi.IPackageImport;
import org.apache.felix.sigil.common.model.osgi.IRequiredBundle;
import org.apache.felix.sigil.common.repository.IRepositoryManager;
import org.apache.felix.sigil.common.repository.IResolution;
import org.apache.felix.sigil.common.repository.ResolutionConfig;
import org.apache.felix.sigil.common.repository.ResolutionException;
import org.apache.felix.sigil.eclipse.PathUtil;
import org.apache.felix.sigil.eclipse.SigilCore;
import org.apache.felix.sigil.eclipse.job.ThreadProgressMonitor;
import org.apache.felix.sigil.eclipse.model.project.ISigilProjectModel;
import org.apache.felix.sigil.eclipse.model.util.JavaHelper;
import org.apache.felix.sigil.eclipse.progress.ProgressAdapter;
import org.apache.felix.sigil.eclipse.repository.ResolutionMonitorAdapter;
import org.apache.felix.sigil.utils.GlobCompiler;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.jdt.core.ClasspathContainerInitializer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IParent;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.osgi.framework.Version;
/**
* @author dave
*
*/
public class SigilProject extends AbstractCompoundModelElement implements ISigilProjectModel
{
private static final long serialVersionUID = 1L;
private IFile bldProjectFile;
private IProject project;
private IBldProject bldProject;
private ISigilBundle bundle;
private List<IRequirementModelElement> lastReqs = new LinkedList<IRequirementModelElement>();
private List<ICapabilityModelElement> lastCaps = new LinkedList<ICapabilityModelElement>();
public SigilProject()
{
super("Sigil Project");
}
public SigilProject(IProject project) throws CoreException
{
this();
this.project = project;
bldProjectFile = project.getFile(new Path(SigilCore.SIGIL_PROJECT_FILE));
}
public void save(IProgressMonitor monitor) throws CoreException
{
save(monitor, true);
}
public void save(IProgressMonitor monitor, boolean rebuildDependencies)
throws CoreException
{
SubMonitor progress = SubMonitor.convert(monitor, 1000);
if (bldProjectFile.getLocation().toFile().exists())
{
bldProjectFile.setContents(buildContents(), IFile.KEEP_HISTORY,
progress.newChild(10));
}
else
{
bldProjectFile.create(buildContents(), true /* force */,
progress.newChild(5));
project.refreshLocal(IResource.DEPTH_ONE, progress.newChild(5));
}
if (rebuildDependencies)
{
rebuildDependencies(progress.newChild(900));
}
}
public void rebuildDependencies(IProgressMonitor monitor) throws CoreException
{
SubMonitor progress = SubMonitor.convert(monitor, 1000);
HashSet<ICapabilityModelElement> changes = new HashSet<ICapabilityModelElement>(
lastCaps);
LinkedList<IRequirementModelElement> reqs = new LinkedList<IRequirementModelElement>();
LinkedList<ICapabilityModelElement> caps = new LinkedList<ICapabilityModelElement>();
checkChanges(progress.newChild(100), reqs, caps);
boolean reqsChanged;
boolean capsChanged;
synchronized (this)
{
reqsChanged = isRequirementsChanged(reqs);
capsChanged = isCapabilitiesChanged(caps);
}
if (reqsChanged)
{
processRequirementsChanges(progress.newChild(600));
SigilCore.rebuild(this, progress.newChild(50));
}
progress.setWorkRemaining(250);
if (capsChanged)
{
changes.addAll(caps);
SigilCore.rebuildBundleDependencies(this, changes, progress.newChild(250));
}
}
public void flushDependencyState()
{
synchronized (this)
{
lastReqs.clear();
}
}
private void processRequirementsChanges(IProgressMonitor monitor)
throws CoreException
{
SubMonitor progress = SubMonitor.convert(monitor, 100);
IRepositoryManager manager = getRepositoryManager();
ResolutionConfig config = new ResolutionConfig(ResolutionConfig.INCLUDE_OPTIONAL
| ResolutionConfig.IGNORE_ERRORS);
try
{
IResolution resolution = manager.getBundleResolver().resolve(this, config,
new ResolutionMonitorAdapter(progress.newChild(20)));
markProblems(resolution);
// pull remote bundles from repositories to be added to classpath
if (!resolution.isSynchronized())
{
resolution.synchronize(new ProgressAdapter(progress.newChild(80)));
}
}
catch (ResolutionException e)
{
throw SigilCore.newCoreException("Failed to resolve dependencies", e);
}
}
private void markProblems(IResolution resolution)
{
try
{
getProject().deleteMarkers(SigilCore.MARKER_UNRESOLVED_DEPENDENCY, true,
IResource.DEPTH_ONE);
// Find missing imports
Collection<IPackageImport> imports = getBundle().getBundleInfo().getImports();
for (IPackageImport pkgImport : imports)
{
if (resolution.getProvider(pkgImport) == null)
{
markMissingImport(pkgImport, getProject());
}
}
// Find missing required bundles
Collection<IRequiredBundle> requiredBundles = getBundle().getBundleInfo().getRequiredBundles();
for (IRequiredBundle requiredBundle : requiredBundles)
{
if (resolution.getProvider(requiredBundle) == null)
{
markMissingRequiredBundle(requiredBundle, getProject());
}
}
}
catch (CoreException e)
{
SigilCore.error("Failed to update problems", e);
}
}
private void checkChanges(IProgressMonitor monitor,
final List<IRequirementModelElement> reqs,
final List<ICapabilityModelElement> caps)
{
visit(new IModelWalker()
{
public boolean visit(IModelElement element)
{
if (element instanceof IRequirementModelElement)
{
reqs.add((IRequirementModelElement) element);
}
else if (element instanceof ICapabilityModelElement)
{
// also calculate uses during this pass to save multi pass on model
if (element instanceof IPackageExport)
{
IPackageExport pe = (IPackageExport) element;
try
{
pe.setUses(Arrays.asList(JavaHelper.findUses(
pe.getPackageName(), SigilProject.this)));
}
catch (CoreException e)
{
SigilCore.error("Failed to build uses list for " + pe, e);
}
}
caps.add((ICapabilityModelElement) element);
}
return true;
}
});
}
private boolean isRequirementsChanged(List<IRequirementModelElement> dependencies)
{
if (lastReqs.equals(dependencies))
{
return false;
}
else
{
lastReqs = dependencies;
return true;
}
}
private boolean isCapabilitiesChanged(List<ICapabilityModelElement> capabilites)
{
if (lastCaps.equals(capabilites))
{
return false;
}
else
{
lastCaps = capabilites;
return true;
}
}
private static void markMissingImport(IPackageImport pkgImport, IProject project)
throws CoreException
{
IMarker marker = project.getProject().createMarker(
SigilCore.MARKER_UNRESOLVED_IMPORT_PACKAGE);
marker.setAttribute("element", pkgImport.getPackageName());
marker.setAttribute("versionRange", pkgImport.getVersions().toString());
marker.setAttribute(IMarker.MESSAGE, "Cannot resolve imported package \""
+ pkgImport.getPackageName() + "\" with version range "
+ pkgImport.getVersions());
marker.setAttribute(IMarker.SEVERITY,
pkgImport.isOptional() ? IMarker.SEVERITY_WARNING : IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
}
private static void markMissingRequiredBundle(IRequiredBundle req, IProject project)
throws CoreException
{
IMarker marker = project.getProject().createMarker(
SigilCore.MARKER_UNRESOLVED_REQUIRE_BUNDLE);
marker.setAttribute("element", req.getSymbolicName());
marker.setAttribute("versionRange", req.getVersions().toString());
marker.setAttribute(IMarker.MESSAGE, "Cannot resolve required bundle \""
+ req.getSymbolicName() + "\" with version range " + req.getVersions());
marker.setAttribute(IMarker.SEVERITY, req.isOptional() ? IMarker.SEVERITY_WARNING
: IMarker.SEVERITY_ERROR);
marker.setAttribute(IMarker.PRIORITY, IMarker.PRIORITY_HIGH);
}
public Collection<IClasspathEntry> findExternalClasspath(IProgressMonitor monitor)
throws CoreException
{
return JavaHelper.resolveClasspathEntrys(this, monitor);
}
public Version getVersion()
{
ISigilBundle bundle = getBundle();
return bundle == null ? null : bundle.getBundleInfo() == null ? null
: bundle.getBundleInfo().getVersion();
}
public String getSymbolicName()
{
ISigilBundle bundle = getBundle();
return bundle == null ? null : bundle.getBundleInfo() == null ? null
: bundle.getBundleInfo().getSymbolicName();
}
public IProject getProject()
{
return project;
}
public ISigilBundle getBundle()
{
ISigilBundle b = null;
try
{
synchronized (bldProjectFile)
{
if (bundle == null)
{
IPath loc = bldProjectFile.getLocation();
if (loc == null) {
// callers can protect against this by using
// checking exists()
throw new IllegalStateException("Sigil project does not exist");
}
else if (loc.toFile().exists())
{
bundle = parseContents(bldProjectFile);
}
else
{
bundle = setupDefaults();
}
}
b = bundle;
}
}
catch (CoreException e)
{
SigilCore.error("Failed to build bundle", e);
}
return b;
}
public void setBundle(ISigilBundle bundle)
{
synchronized (bldProjectFile)
{
this.bundle = bundle;
}
}
public IJavaProject getJavaModel()
{
return JavaCore.create(project);
}
@Override
public boolean equals(Object obj)
{
if (obj == null)
return false;
if (obj == this)
return true;
try
{
SigilProject p = (SigilProject) obj;
return getSymbolicName().equals(p.getSymbolicName())
&& (getVersion() == null ? p.getVersion() == null : getVersion().equals(
p.getVersion()));
}
catch (ClassCastException e)
{
return false;
}
}
@Override
public int hashCode()
{
String bsn = getSymbolicName();
int hc = bsn == null ? 1 : bsn.hashCode();
if (getVersion() != null)
{
hc *= getVersion().hashCode();
}
hc *= 7;
return hc;
}
@Override
public String toString()
{
return "SigilProject[" + getSymbolicName() + ":" + getVersion() + "]";
}
public void resetClasspath(IProgressMonitor monitor, boolean forceResolve) throws CoreException
{
if (forceResolve) {
processRequirementsChanges(monitor);
}
Path containerPath = new Path(SigilCore.CLASSPATH_CONTAINER_PATH);
IJavaProject java = getJavaModel();
ClasspathContainerInitializer init = JavaCore.getClasspathContainerInitializer(SigilCore.CLASSPATH_CONTAINER_PATH);
ThreadProgressMonitor.setProgressMonitor(monitor);
try
{
init.requestClasspathContainerUpdate(containerPath, java, null);
}
finally
{
ThreadProgressMonitor.setProgressMonitor(null);
}
}
public IPath findBundleLocation() throws CoreException
{
IPath p = PathUtil.newPathIfExists(getBundle().getLocation());
if (p == null)
{
p = SigilCore.getDefault().findDefaultBundleLocation(this);
}
return p;
}
public IModelElement findImport(final String packageName,
final IProgressMonitor monitor)
{
final IModelElement[] found = new IModelElement[1];
visit(new IModelWalker()
{
public boolean visit(IModelElement element)
{
if (element instanceof IPackageImport)
{
IPackageImport pi = (IPackageImport) element;
if (pi.getPackageName().equals(packageName))
{
found[0] = pi;
return false;
}
}
else if (element instanceof IRequiredBundle)
{
IRequiredBundle rb = (IRequiredBundle) element;
try
{
IRepositoryManager manager = SigilProject.this.getRepositoryManager();
ResolutionConfig config = new ResolutionConfig(
ResolutionConfig.IGNORE_ERRORS);
IResolution res = manager.getBundleResolver().resolve(rb, config,
new ResolutionMonitorAdapter(monitor));
ISigilBundle b = res.getProvider(rb);
for (IPackageExport pe : b.getBundleInfo().getExports())
{
if (pe.getPackageName().equals(packageName))
{
found[0] = rb;
return false;
}
}
}
catch (ResolutionException e)
{
SigilCore.error("Failed to resolve " + rb, e);
}
}
return true;
}
});
return found[0];
}
public boolean isInClasspath(String packageName, IProgressMonitor monitor)
throws CoreException
{
if (findImport(packageName, monitor) != null)
{
return true;
}
for (String path : getBundle().getClasspathEntrys())
{
IClasspathEntry cp = getJavaModel().decodeClasspathEntry(path);
for (IPackageFragmentRoot root : getJavaModel().findPackageFragmentRoots(cp))
{
if (findPackage(packageName, root))
{
return true;
}
}
}
return false;
}
public boolean isInClasspath(ISigilBundle bundle)
{
for (String path : getBundle().getClasspathEntrys())
{
IClasspathEntry cp = getJavaModel().decodeClasspathEntry(path);
switch (cp.getEntryKind())
{
case IClasspathEntry.CPE_PROJECT:
ISigilProjectModel p = bundle.getAncestor(ISigilProjectModel.class);
return p != null && cp.getPath().equals(p.getProject().getFullPath());
case IClasspathEntry.CPE_LIBRARY:
return cp.getPath().equals(bundle.getLocation());
}
}
return false;
}
private boolean findPackage(String packageName, IParent parent)
throws JavaModelException
{
for (IJavaElement e : parent.getChildren())
{
if (e.getElementType() == IJavaElement.PACKAGE_FRAGMENT)
{
return e.getElementName().equals(packageName);
}
if (e instanceof IParent)
{
if (findPackage(packageName, (IParent) e))
{
return true;
}
}
}
return false;
}
private ISigilBundle setupDefaults()
{
ISigilBundle bundle = ModelElementFactory.getInstance().newModelElement(
ISigilBundle.class);
IBundleModelElement info = ModelElementFactory.getInstance().newModelElement(
IBundleModelElement.class);
bundle.setBundleInfo(info);
bundle.setParent(this);
return bundle;
}
private ISigilBundle parseContents(IFile projectFile) throws CoreException
{
if (projectFile.getName().equals(SigilCore.SIGIL_PROJECT_FILE))
{
return parseBldContents(projectFile.getLocationURI());
}
else
{
throw SigilCore.newCoreException("Unexpected project file: "
+ projectFile.getName(), null);
}
}
private ISigilBundle parseBldContents(URI uri) throws CoreException
{
try
{
bldProject = BldFactory.getProject(uri, true);
ISigilBundle bundle = bldProject.getDefaultBundle();
if (bundle == null)
{
throw SigilCore.newCoreException("No default bundle", null);
}
bundle.setParent(this);
return bundle;
}
catch (IOException e)
{
throw SigilCore.newCoreException("Failed to parse " + uri, e);
}
}
private InputStream buildContents() throws CoreException
{
ByteArrayOutputStream buf = new ByteArrayOutputStream();
try
{
if (bldProject == null)
{
bldProject = BldFactory.newProject(bldProjectFile.getLocationURI(), null);
}
bldProject.setDefaultBundle(getBundle());
bldProject.saveTo(buf);
}
catch (IOException e)
{
throw SigilCore.newCoreException("Failed to save project file", e);
}
return new ByteArrayInputStream(buf.toByteArray());
}
public String getName()
{
return getProject().getName();
}
public IPath findOutputLocation() throws CoreException
{
return getProject().getLocation().append(
getJavaModel().getOutputLocation().removeFirstSegments(1));
}
public IBldProject getBldProject() throws CoreException
{
try
{
return BldFactory.getProject(project.getFile(IBldProject.PROJECT_FILE).getLocationURI());
}
catch (IOException e)
{
throw SigilCore.newCoreException("Failed to get project file: ", e);
}
}
public IRepositoryConfig getRepositoryConfig() throws CoreException
{
try
{
return BldFactory.getConfig(project.getFile(IBldProject.PROJECT_FILE).getLocationURI());
}
catch (IOException e)
{
throw SigilCore.newCoreException("Failed to get project file: ", e);
}
}
public boolean isInBundleClasspath(IPackageFragment root) throws JavaModelException
{
if (getBundle().getClasspathEntrys().isEmpty())
{
for (String p : getBundle().getPackages())
{
SigilCore.log("Checking " + p + "->" + root.getElementName());
Matcher m = GlobCompiler.compile(p).matcher(root.getElementName());
if (m.matches())
{
return true;
}
}
return false;
}
else
{
IPackageFragmentRoot parent = (IPackageFragmentRoot) root.getParent();
String enc = getJavaModel().encodeClasspathEntry(
parent.getRawClasspathEntry());
return getBundle().getClasspathEntrys().contains(enc.trim());
}
}
/* (non-Javadoc)
* @see org.apache.felix.sigil.eclipse.model.project.ISigilProjectModel#getRepositoryManager()
*/
public IRepositoryManager getRepositoryManager()
{
return SigilCore.getRepositoryManager(this);
}
/* (non-Javadoc)
* @see org.apache.felix.sigil.eclipse.model.project.ISigilProjectModel#exists()
*/
public boolean exists()
{
return project.exists() && project.getFile(IBldProject.PROJECT_FILE).exists();
}
}