/******************************************************************************* * Copyright (c) 2000, 2004 IBM Corporation and others. * 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: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.erlide.engine.internal.model.root; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.EnumSet; import java.util.List; import org.eclipse.core.resources.ICommand; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IProjectDescription; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ProjectScope; import org.eclipse.core.resources.ResourcesPlugin; 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.preferences.IEclipsePreferences; import org.erlide.engine.ErlangEngine; import org.erlide.engine.internal.ModelPlugin; import org.erlide.engine.internal.model.cache.ErlModelCache; import org.erlide.engine.model.ErlElementKind; import org.erlide.engine.model.ErlModelException; import org.erlide.engine.model.ErlModelStatus; import org.erlide.engine.model.ErlModelStatusConstants; import org.erlide.engine.model.IErlElement; import org.erlide.engine.model.IErlElementVisitor; import org.erlide.engine.model.IParent; import org.erlide.engine.model.SourcePathUtils; import org.erlide.engine.model.builder.BuilderProperties; import org.erlide.engine.model.erlang.SourceKind; import org.erlide.engine.model.root.ErlangProjectProperties; import org.erlide.engine.model.root.IErlElementLocator; import org.erlide.engine.model.root.IErlExternalRoot; import org.erlide.engine.model.root.IErlFolder; import org.erlide.engine.model.root.IErlModel; import org.erlide.engine.model.root.IErlModule; import org.erlide.engine.model.root.IErlProject; import org.erlide.engine.model.root.IOpenable; import org.erlide.engine.model.root.IProjectConfigurator; import org.erlide.engine.model.root.PathResolver; import org.erlide.engine.model.root.ProjectConfigType; import org.erlide.engine.model.root.ProjectConfigurationChangeListener; import org.erlide.engine.services.search.OpenService; import org.erlide.engine.util.CommonUtils; import org.erlide.engine.util.NatureUtil; import org.erlide.runtime.api.RuntimeCore; import org.erlide.runtime.runtimeinfo.RuntimeInfo; import org.erlide.runtime.runtimeinfo.RuntimeVersion; import org.erlide.util.ErlLogger; import org.osgi.service.prefs.BackingStoreException; import com.google.common.collect.Lists; /** * Handle for an Erlang project. * * <p> * A Erlang Project internally maintains a devpath that corresponds to the * project's classpath. The classpath may include source folders from the * current project; archives in the current project, other projects, and the * local file system; and binary folders (output location) of other projects. * The Erlang Model presents source elements corresponding to output .beam files * in other projects, and thus uses the devpath rather than the classpath (which * is really a compilation path). The devpath mimics the classpath, except has * source folder entries in place of output locations in external projects. * * <p> * Each ErlProject has a NameLookup facility that locates elements on by name, * based on the devpath. * * @see IErlProject */ public class ErlProject extends Openable implements IErlProject, ProjectConfigurationChangeListener { private static final String CONFIG_TYPE_TAG = "configType"; /** * Whether the underlying file system is case sensitive. */ private static final boolean IS_CASE_SENSITIVE = !new File("Temp") //$NON-NLS-1$ .equals(new File("temp")); //$NON-NLS-1$ protected IProject fProject; private ProjectConfigType configType = ProjectConfigType.INTERNAL; private ErlangProjectProperties properties; private BuilderProperties builderProperties; private volatile boolean configuring = false; public ErlProject(final IProject project, final IParent parent) { super(parent, project.getName()); fProject = project; } @Override public boolean buildStructure(final IProgressMonitor pm) throws ErlModelException { final IResource r = getCorrespondingResource(); // check whether the Erlang project can be opened if (!(r instanceof IContainer) || !r.isAccessible()) { ErlLogger.warn("Project %s has no resources: res:%s acc:%s cont:%s", getName(), r, r == null ? "?" : r.isAccessible(), r instanceof IContainer); throw new ErlModelException(new ErlModelStatus( ErlModelStatusConstants.ELEMENT_DOES_NOT_EXIST, this)); } addConfigurationChangeListeners(); try { final IContainer c = (IContainer) r; final IResource[] elems = c.members(); final List<IErlElement> children = new ArrayList<>( elems.length + 1); // ErlLogger.debug(">>adding externals"); addExternals(children); // ErlLogger.debug("childcount %d", children.size()); // ErlLogger.debug(">>adding otp"); addOtpExternals(children); // ErlLogger.debug("childcount %d", children.size()); final IErlModel model = ErlangEngine.getInstance().getModel(); for (final IResource element : elems) { if (element instanceof IFolder) { final IFolder folder = (IFolder) element; final IErlFolder erlFolder = (IErlFolder) model.create(folder); children.add(erlFolder); } else if (element instanceof IFile) { final IFile file = (IFile) element; if (CommonUtils.isErlangFileContentFileName(file.getName())) { final IErlModule m = (IErlModule) model.create(file); children.add(m); } } } setChildren(children); } catch (final CoreException e) { ErlLogger.error(e); setChildren(new ArrayList<IErlModule>()); return false; } return true; } private void addOtpExternals(final List<IErlElement> children) { final String name = "OTP " + getRuntimeVersion().toString(); IErlElement external = getChildNamed(name); if (external == null) { external = new ErlOtpExternalReferenceEntryList(this, name); } children.add(external); } private void addExternals(final List<IErlElement> children) { final ErlangProjectProperties myProperties = getProperties(); final String externalIncludes = myProperties.getExternalIncludes(); final String externalModules = myProperties.getExternalModules(); final Collection<IPath> includeDirs = myProperties.getIncludeDirs(); final List<String> projectIncludes = Lists.newArrayList(); for (final IPath path : new PathResolver().resolvePaths(includeDirs)) { if (path.isAbsolute() && !fProject.getLocation().isPrefixOf(path)) { final Collection<String> includes = ErlangEngine.getInstance() .getService(OpenService.class) .getIncludesInDir(path.toPortableString()); if (includes != null) { for (final String include : includes) { projectIncludes.add(path.append(include).toPortableString()); } } } } if (externalIncludes.length() != 0 || externalModules.length() != 0 || !projectIncludes.isEmpty()) { final IErlExternalRoot external = new ErlExternalReferenceEntryList(this, "Externals", externalIncludes, projectIncludes, externalModules); children.add(external); } } /** * Removes the Erlang nature from the project. */ public void deconfigure() throws CoreException { // unregister Erlang builder removeFromBuildSpec(ModelPlugin.BUILDER_ID); } /** * Returns a default output location. This is the project bin folder */ protected IPath defaultOutputLocation() { return fProject.getFullPath().append("ebin"); //$NON-NLS-1$ } /** * Returns true if this handle represents the same Erlang project as the * given handle. Two handles represent the same project if they are * identical or if they represent a project with the same underlying * resource and occurrence counts. * * @see ErlElement#equals(Object) */ @Override public boolean equals(final Object o) { if (this == o) { return true; } if (!(o instanceof ErlProject)) { return false; } final ErlProject other = (ErlProject) o; return fProject.equals(other.getWorkspaceProject()); } @Override public boolean exists() { return NatureUtil.hasErlangNature(fProject); } /** * @see IErlElement */ @Override public ErlElementKind getKind() { return ErlElementKind.PROJECT; } /** * @see IErlElement */ @Override public IResource getResource() { return getCorrespondingResource(); } @Override public IResource getCorrespondingResource() { return fProject; } @Override public int hashCode() { if (fProject == null) { return super.hashCode(); } return fProject.hashCode(); } /** * Removes the given builder from the build spec for the given project. */ protected void removeFromBuildSpec(final String builderID) throws CoreException { final IProjectDescription description = fProject.getDescription(); final ICommand[] commands = description.getBuildSpec(); for (int i = 0; i < commands.length; ++i) { if (commands[i].getBuilderName().equals(builderID)) { final ICommand[] newCommands = new ICommand[commands.length - 1]; System.arraycopy(commands, 0, newCommands, 0, i); System.arraycopy(commands, i + 1, newCommands, i, commands.length - i - 1); description.setBuildSpec(newCommands); fProject.setDescription(description, null); return; } } } /** * Answers an PLUGIN_ID which is used to distinguish project/entries during * package fragment root computations * * @return String */ public String rootID() { return "[PRJ]" + fProject.getFullPath(); //$NON-NLS-1$ } @Override public Collection<IErlModule> getModules() throws ErlModelException { final List<IErlModule> modulesForProject = ErlModelCache.getDefault() .getModulesForProject(this); if (modulesForProject != null) { return modulesForProject; } final List<IErlModule> result = new ArrayList<>(); final List<IPath> sourceDirs = Lists .newArrayList(getProperties().getSourceDirs()); for (final IPath s : SourcePathUtils.getExtraSourcePathsForModel(fProject)) { sourceDirs.add(s); } result.addAll(getModulesOrIncludes(fProject, ErlangEngine.getInstance().getModel(), sourceDirs, true)); ErlModelCache.getDefault().putModulesForProject(this, result); return result; } private static List<IErlModule> getModulesOrIncludes(final IProject project, final IErlElementLocator model, final Collection<IPath> dirs, final boolean getModules) throws ErlModelException { final List<IErlModule> result = Lists.newArrayList(); for (final IPath dir : dirs) { final IFolder folder = project.getFolder(dir); final IErlElement element = model.findElement(folder, true); if (element instanceof IErlFolder) { final IErlFolder erlFolder = (IErlFolder) element; erlFolder.open(null); for (final IErlElement e : erlFolder .getChildrenOfKind(ErlElementKind.MODULE)) { if (e instanceof IErlModule) { final IErlModule m = (IErlModule) e; final boolean isModule = SourceKind .nameToModuleKind(m.getName()) != SourceKind.HRL; if (isModule == getModules) { result.add(m); } } } } } return result; } @Override public Collection<IErlModule> getModulesAndIncludes() throws ErlModelException { final List<IErlModule> result = new ArrayList<>(); final ErlModelCache erlModelCache = ErlModelCache.getDefault(); final List<IErlModule> modulesForProject = erlModelCache .getModulesForProject(this); final List<IErlModule> includesForProject = erlModelCache .getIncludesForProject(this); if (modulesForProject != null && includesForProject != null) { result.addAll(modulesForProject); result.addAll(includesForProject); } else { final List<IErlModule> cached = erlModelCache.getModulesForProject(this); final IErlElementLocator model = ErlangEngine.getInstance().getModel(); if (cached == null) { final List<IErlModule> modules = getModulesOrIncludes(fProject, model, getProperties().getSourceDirs(), true); result.addAll(modules); } else { result.addAll(cached); } final Collection<IErlModule> includes = getIncludes(); result.addAll(includes); } return result; } @Override public Collection<IErlModule> getIncludes() throws ErlModelException { final ErlModelCache erlModelCache = ErlModelCache.getDefault(); final List<IErlModule> cached = erlModelCache.getIncludesForProject(this); if (cached != null) { return cached; } final List<IErlModule> includes = getModulesOrIncludes(fProject, ErlangEngine.getInstance().getModel(), getProperties().getIncludeDirs(), false); erlModelCache.putIncludesForProject(this, includes); return includes; } /** * Returns a canonicalized path from the given external path. Note that the * return path contains the same number of segments and it contains a device * only if the given path contained one. * * @param externalPath * IPath * @see java.io.File for the definition of a canonicalized path * @return IPath */ public static IPath canonicalizedPath(final IPath externalPath) { if (externalPath == null) { return null; } if (IS_CASE_SENSITIVE) { return externalPath; } // if not external path, return original path final IWorkspace workspace = ResourcesPlugin.getWorkspace(); if (workspace == null) { return externalPath; // protection during shutdown (30487) } if (workspace.getRoot().findMember(externalPath) != null) { return externalPath; } IPath canonicalPath = null; try { canonicalPath = new Path( new File(externalPath.toOSString()).getCanonicalPath()); } catch (final IOException e) { // default to original path return externalPath; } IPath result; final int canonicalLength = canonicalPath.segmentCount(); if (canonicalLength == 0) { // the java.io.File canonicalization failed return externalPath; } else if (externalPath.isAbsolute()) { result = canonicalPath; } else { // if path is relative, remove the first segments that were added by // the java.io.File canonicalization // e.g. 'lib/classes.zip' was converted to // 'd:/myfolder/lib/classes.zip' final int externalLength = externalPath.segmentCount(); if (canonicalLength >= externalLength) { result = canonicalPath .removeFirstSegments(canonicalLength - externalLength); } else { return externalPath; } } // keep device only if it was specified (this is because // File.getCanonicalPath() converts '/lib/classed.zip' to // 'd:/lib/classes/zip') if (externalPath.getDevice() == null) { result = result.setDevice(null); } return result; } @Override public IErlModule getModule(final String name) { try { return ErlangEngine.getInstance().getModel().findModuleFromProject(this, name, null, false, IErlElementLocator.Scope.PROJECT_ONLY); } catch (final ErlModelException e) { // final boolean hasExtension = ListsUtils.hasExtension(name); return null; } } @Override public ErlangProjectProperties getProperties() { if (properties == null) { configurationChanged(); } return properties; } public IEclipsePreferences getCorePropertiesNode() { return new ProjectScope(fProject).getNode("org.erlide.model"); } @Override public Collection<IErlModule> getExternalModules() throws ErlModelException { final List<IErlModule> result = Lists.newArrayList(); accept(new IErlElementVisitor() { @Override public boolean visit(final IErlElement element) throws ErlModelException { final boolean isExternalOrProject = element .getKind() == ErlElementKind.EXTERNAL_ROOT || element.getKind() == ErlElementKind.EXTERNAL_APP || element.getKind() == ErlElementKind.EXTERNAL_FOLDER || element.getKind() == ErlElementKind.PROJECT; if (element instanceof IErlModule) { final IErlModule module = (IErlModule) element; result.add(module); return false; } else if (isExternalOrProject && element instanceof IOpenable) { final IOpenable openable = (IOpenable) element; openable.open(null); } return isExternalOrProject; } }, EnumSet.noneOf(AcceptFlags.class), ErlElementKind.MODULE); return result; } @Override public void resourceChanged(final IResourceDelta delta) { if (delta == null) { return; } if ((delta.getFlags() & IResourceDelta.DESCRIPTION) != 0) { // TODO when we have cache in ErlModuleMap for referenced projects, // we should purge it here } if ((delta.getFlags() & ~IResourceDelta.MARKERS) != 0) { super.resourceChanged(delta); // FIXME when should we call getModelCache().removeProject(this); ? } } public void setIncludeDirs(final Collection<IPath> includeDirs) { getModelCache().removeProject(this); properties.setIncludeDirs(includeDirs); storeProperties(); setStructureKnown(false); } public void setSourceDirs(final Collection<IPath> sourceDirs) { getModelCache().removeProject(this); properties.setSourceDirs(sourceDirs); storeProperties(); setStructureKnown(false); } public void setExternalModulesFile(final String absolutePath) { getModelCache().removeProject(this); properties.setExternalModulesFile(absolutePath); storeProperties(); setStructureKnown(false); } public void setExternalIncludesFile(final String absolutePath) { getModelCache().removeProject(this); properties.setExternalIncludesFile(absolutePath); storeProperties(); setStructureKnown(false); } RuntimeVersion cachedRuntimeVersion; RuntimeInfo cachedRuntimeInfo; private boolean storing; @Override public RuntimeInfo getRuntimeInfo() { final RuntimeVersion requiredRuntimeVersion = getProperties() .getRequiredRuntimeVersion(); if (requiredRuntimeVersion != cachedRuntimeVersion) { cachedRuntimeVersion = requiredRuntimeVersion; cachedRuntimeInfo = RuntimeCore.getRuntimeInfoCatalog() .getRuntime(requiredRuntimeVersion, null); } return cachedRuntimeInfo; } @Override public RuntimeVersion getRuntimeVersion() { final RuntimeInfo runtimeInfo = getRuntimeInfo(); if (runtimeInfo != null) { return runtimeInfo.getVersion(); } // should not happen return null; } @Override public void setProperties(final ErlangProjectProperties newProperties) { getModelCache().removeProject(this); if (properties == null) { properties = newProperties; } else { properties.copyFrom(newProperties); } storeAllProperties(); } @Override public void clearCaches() { getModelCache().removeProject(this); } @Override public Collection<IErlProject> getReferencedProjects() throws ErlModelException { final List<IErlProject> result = Lists.newArrayList(); try { for (final IProject project : fProject.getReferencedProjects()) { final IErlProject p = ErlangEngine.getInstance().getModel() .getErlangProject(project); if (p != null) { result.add(p); } } } catch (final CoreException e) { throw new ErlModelException(e); } return result; } @Override public Collection<IErlModule> getExternalIncludes() throws ErlModelException { final List<IErlModule> result = Lists.newArrayList(); accept(new IErlElementVisitor() { @Override public boolean visit(final IErlElement element) throws ErlModelException { final boolean isExternalOrProject = element.getKind() == ErlElementKind.EXTERNAL_ROOT || element.getKind() == ErlElementKind.EXTERNAL_APP || element.getKind() == ErlElementKind.EXTERNAL_FOLDER || element.getKind() == ErlElementKind.PROJECT; if (element instanceof IErlModule) { final IErlModule module = (IErlModule) element; if (module.getSourceKind() == SourceKind.HRL) { result.add(module); } return false; } else if (isExternalOrProject && element instanceof IOpenable) { final IOpenable openable = (IOpenable) element; openable.open(null); } return isExternalOrProject; } }, EnumSet.noneOf(AcceptFlags.class), ErlElementKind.MODULE); return result; } public void pathVarsChanged() { clearCaches(); } public boolean moduleInProject(final IErlModule module) { final IErlProject project = ErlangEngine.getInstance().getModelUtilService() .getProject(module); if (project == null) { return false; } return equals(project); } @Override public void dispose() { removeConfigurationChangeListeners(); clearCaches(); try { accept(new IErlElementVisitor() { @Override public boolean visit(final IErlElement element) throws ErlModelException { element.dispose(); return false; } }, EnumSet.of(AcceptFlags.CHILDREN_FIRST, AcceptFlags.LEAFS_ONLY), ErlElementKind.MODULE); } catch (final ErlModelException e) { } super.dispose(); } private void addConfigurationChangeListeners() { // TODO listen for changes to config files/prefs -- config specific?? } private void removeConfigurationChangeListeners() { // TODO remove listeners above } @Override public IProject getWorkspaceProject() { return fProject; } @Override public void close() throws ErlModelException { clearCaches(); super.close(); } private void loadCoreProperties() { final IEclipsePreferences node = getCorePropertiesNode(); final String name = node.get(CONFIG_TYPE_TAG, ProjectConfigType.INTERNAL.name()); setConfigType(ProjectConfigType.valueOf(name)); } private void storeCoreProperties() { final IEclipsePreferences node = getCorePropertiesNode(); node.put(CONFIG_TYPE_TAG, getConfigType().name()); try { node.flush(); } catch (final Exception e) { ErlLogger.debug(e); // ignore? } } @Override public void setConfigType(final ProjectConfigType config) { if (configType != config) { configType = config; storeCoreProperties(); } } @Override public ProjectConfigType getConfigType() { return configType; } private ErlangProjectProperties loadProperties() { final IProjectConfigurator builderConfig = getConfig(); return builderConfig.getConfiguration(); } private IProjectConfigurator getConfig() { return ErlangEngine.getInstance().getProjectConfiguratorFactory() .getConfig(getConfigType(), this); } private void storeProperties() { if (properties != null) { final IProjectConfigurator builderConfig = getConfig(); builderConfig.setConfiguration(properties); } } @Override public void configurationChanged() { if (configuring || storing) { return; } try { configuring = true; loadAllProperties(); } finally { configuring = false; } } private void loadAllProperties() { loadCoreProperties(); loadBuilderProperties(); properties = loadProperties(); } @Override public void storeAllProperties() { if (storing) { return; } try { storing = true; storeCoreProperties(); storeBuilderProperties(); storeProperties(); } finally { storing = false; } } private void loadBuilderProperties() { final IEclipsePreferences node = getCorePropertiesNode(); final String data = node.get("builderData", ""); builderProperties = new BuilderProperties(); builderProperties.fromString(data); // TODO more } private void storeBuilderProperties() { final IEclipsePreferences node = getCorePropertiesNode(); node.put("builderData", builderProperties.toString()); try { node.flush(); } catch (final BackingStoreException e) { // ignore? } } @Override public BuilderProperties getBuilderProperties() { if (builderProperties == null) { loadBuilderProperties(); } return builderProperties; } @Override public void setBuilderProperties(final BuilderProperties props) { builderProperties = props; } }