/* This file is part of the OpenJML plugin project.
* Copyright (c) 2006-2013 David R. Cok
* @author David R. Cok
*/
package org.jmlspecs.openjml.eclipse;
import java.io.File;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
// FIXME - would like to have paths with variables, e.g. ${user.dir}
// FIXME - Source paths do not include items in referenced projects
// FIXME - Source paths ignore the included/excluded declarations in the Java Build Path
/** These classes manage components of the source and specs paths, much as Eclipse
* has different kinds of elements of the classpath. The paths have a serialized
* text version that is stored in a persistent property of the Java project.
*/
abstract public class PathItem {
/** The string that separates the representation of the kind of PathItem
* from the value.
*/
public final static String sep = "#"; // Presumed to be a single character //$NON-NLS-1$
/** The separator that separates elements of the paths in the serialized
* version; this character may not appear in a file name.
*/
public final static String split = ","; //$NON-NLS-1$
/** Creates an appropriate PathItem from a raw file path. */
public static PathItem create(IJavaProject jp, String pathString) {
IFile f = ResourcesPlugin.getWorkspace().getRoot().getFileForLocation(new Path(pathString));
if (f != null) {
if (!f.getProject().equals(jp.getProject())) {
return new WorkspacePath(f.getFullPath().toString());
}
return new ProjectPath(f.getProjectRelativePath().toString());
}
return new AbsolutePath(pathString);
}
/** Adds a PathItem to the path for the given key and project; returns
* false if the item was already present, true if it was added successfully. */
public static boolean add(IJavaProject jproject, QualifiedName key, PathItem p) throws CoreException {
String s = getEncodedPath(jproject,key);
String sp = p.toEncodedString();
if (s.isEmpty()) {
s = sp;
} else {
// Check whether the item is already present
// Return false if it is and don't add it
int k = s.indexOf(sp);
if (k >= 0) {
int kk = s.indexOf(split,k);
if (kk == -1) kk = s.length();
int kb = s.lastIndexOf(split,k);
if ((k == 0 || kb + split.length() == k) &&
(kk == k + sp.length())) return false;
}
s = s + split + sp;
}
jproject.getProject().setPersistentProperty(key,s);
return true;
}
/** Removes a PathItem from the path for the given key and project; returns
* false if the item was not present, true if it was removed successfully. */
public static boolean remove(IJavaProject jproject, QualifiedName key, PathItem p) throws CoreException {
String s = getEncodedPath(jproject,key);
String sp = p.toEncodedString();
int k = s.indexOf(sp);
if (k == -1) return false;
if (k != 0 && k != s.lastIndexOf(split,k) + split.length()) return false;
int kkb = k == 0 ? 0 : k - split.length();
int kk = s.indexOf(split,k);
if (kk == -1) kk = s.length();
if (k + sp.length() != kk) return false;
s = s.substring(0,kkb) + s.substring(kk);
jproject.getProject().setPersistentProperty(key,s);
return true;
}
/** Parses an element of a serialized (encoded) String as read from the
* persistent property.
*/
public static /*@ nullable */PathItem parse(String encodedString) {
int k = encodedString.indexOf(sep);
if (k == -1) return null;
String s = encodedString.substring(0,k);
String rest = encodedString.substring(k+sep.length());
if (s.equals(AbsolutePath.prefix)) {
return new AbsolutePath(rest);
} else if (s.equals(ProjectPath.prefix)) {
return new ProjectPath(rest);
} else if (s.equals(WorkspacePath.prefix)) {
return new WorkspacePath(rest);
} else if (s.equals(SpecialPath.prefix)) {
return new SpecialPath(rest);
} else {
return null;
}
}
/** Parses an entire encoded String into a sequence of PathItems */
public static List<PathItem> parseAll(String encodedString) {
if (encodedString == null) encodedString = Utils.emptyString;
List<PathItem> list = new LinkedList<PathItem>();
for (String s: encodedString.split(PathItem.split)) {
/*@ nullable */ PathItem p = parse(s);
if (p != null) list.add(p); // defensive - we should never see a null
}
return list;
}
/** Concatenates the path items into an encoded String */
public static String concat(java.util.List<PathItem> items) {
StringBuilder sb = new StringBuilder();
for (PathItem i: items) {
sb.append(i.toEncodedString());
sb.append(PathItem.split);
}
if (!items.isEmpty()) sb.setLength(sb.length()-PathItem.split.length());
return sb.toString();
}
/** The items in the default sourcepath */
static List<PathItem> defaultSourcePath = new ArrayList<PathItem>();
static {
defaultSourcePath.add(new SpecialPath(SpecialPath.Kind.ALL_SOURCE_FOLDERS));
}
/** The items in the default specspath */
static List<PathItem> defaultSpecsPath = new ArrayList<PathItem>();
static {
defaultSpecsPath.add(new SpecialPath(SpecialPath.Kind.SOURCEPATH));
defaultSpecsPath.add(new SpecialPath(SpecialPath.Kind.SYSTEM_SPECS));
}
/** Writes out the PathItem as it should be shown to the user. */
abstract public String display();
/** Writes out the PathItem as an absolute path. */
abstract public String toAbsolute(IJavaProject jproject);
/** Writes out the PathItem as it is serialized. */
abstract public String toEncodedString();
/** Returns the full serialized path for the given key and project,
* reading it from the property store or supplying a default. */
static public String getEncodedPath(IJavaProject jproject, QualifiedName key) {
try {
String prop = jproject.getProject().getPersistentProperty(key);
if (prop == null) {
List<PathItem> defaults = key.equals(Env.specsKey) ? PathItem.defaultSpecsPath :
key.equals(Env.sourceKey) ? PathItem.defaultSourcePath :
new ArrayList<PathItem>();
prop = concat(defaults);
}
return prop;
} catch (CoreException e) {
Log.errorlog("Exception while retreiving path property: " + key, e); //$NON-NLS-1$
return Utils.emptyString;
}
}
/** Stores the encoded path as a project property. */
static public void setEncodedPath(IJavaProject jproject, QualifiedName key, String encoded) {
try {
jproject.getProject().setPersistentProperty(key,encoded);
} catch (CoreException e) {
Log.errorlog("Exception while setting path property: " + key + Utils.space + encoded, e); //$NON-NLS-1$
}
}
/** Gets the full concatenated (with the platform dependent path separator)
* directory (and jar file) path (as absolute paths) for the given project and key.
*/
static public String getAbsolutePath(IJavaProject jproject, QualifiedName key) {
String stored = getEncodedPath(jproject,key);
String[] paths = stored.split(split);
StringBuilder sb = new StringBuilder();
for (String s: paths) {
PathItem p = parse(s);
if (p == null) {
Log.errorlog("Unexpected failure to parse an persistent encoded path: " + s, null); //$NON-NLS-1$
} else {
sb.append(p.toAbsolute(jproject));
sb.append(File.pathSeparator);
}
}
if (paths.length != 0 && sb.length() > File.pathSeparator.length()) {
sb.setLength(sb.length()-File.pathSeparator.length());
}
return sb.toString();
}
/** Represents a simple absolute file-system path */
public static class AbsolutePath extends PathItem {
/** The serialized prefix identifying this kind of PathItem */
final static String prefix = "abs"; //$NON-NLS-1$
/** The actual absolute file-system path */
protected String location;
public AbsolutePath(String pureString) {
location = pureString;
}
@Override
public String toEncodedString() {
return prefix + sep + location;
}
@Override
public String toAbsolute(IJavaProject jproject) {
return location;
}
@Override
public String display() {
return location;
}
}
/** Represents a path that is an element of the project */
public static class ProjectPath extends PathItem {
final static String prefix = "prj"; //$NON-NLS-1$
String projectRelativeLocation;
public ProjectPath(String projectRelativeLocation) {
this.projectRelativeLocation = projectRelativeLocation;
}
public String toEncodedString() {
return prefix + sep + projectRelativeLocation;
}
public String toAbsolute(IJavaProject jproject) {
return jproject.getProject().findMember(projectRelativeLocation).getLocation().toString();
}
public IResource toResource(IJavaProject jproject) {
return jproject.getProject().findMember(projectRelativeLocation);
}
public String display() {
return Messages.OpenJMLUI_PathItem_PROJECT + projectRelativeLocation;
}
}
/** Represents a path that is an element of the workspace */
public static class WorkspacePath extends PathItem {
final static String prefix = "wsp"; //$NON-NLS-1$
String workspaceRelativePath;
public WorkspacePath(String workspaceRelativePath) {
this.workspaceRelativePath = workspaceRelativePath;
}
public String toEncodedString() {
return prefix + sep + workspaceRelativePath;
}
public String toAbsolute(IJavaProject jproject) {
return ResourcesPlugin.getWorkspace().getRoot().findMember(workspaceRelativePath).getLocation().toString();
}
public IResource toResource(IJavaProject jproject) {
return ResourcesPlugin.getWorkspace().getRoot().findMember(workspaceRelativePath);
}
public String display() {
return Messages.OpenJMLUI_PathItem_WORKSPACE + workspaceRelativePath;
}
}
/** Represents special kinds of path items: the classpath, the sourcepath,
* the system specs, all source folders. Each of these might actually be
* multiple directories or jar files.
*/
public static class SpecialPath extends PathItem {
public static enum Kind {
// If these strings that are the first arguments are changed, then
// stored paths will not be properly reread
ALL_SOURCE_FOLDERS("sf",Messages.OpenJMLUI_PathItem_AllSourceFolders), //$NON-NLS-1$
CLASSPATH("cp",Messages.OpenJMLUI_PathItem_ClassPath), //$NON-NLS-1$
SOURCEPATH("sp",Messages.OpenJMLUI_PathItem_SourcePath), //$NON-NLS-1$
SYSTEM_SPECS("ss",Messages.OpenJMLUI_PathItem_SysSpecs); //$NON-NLS-1$
String key;
String description;
private Kind(String key, String d) { this.key = key; description = d; }
}
final static String prefix = "spc"; //$NON-NLS-1$
Kind kind;
public SpecialPath(Kind kind) {
this.kind = kind;
}
public SpecialPath(String rest) {
for (Kind k : Kind.values()) {
if (rest.equals(k.key)) { kind = k; return; }
}
// Invalid value of rest
Log.errorlog("Invalid SpecialPath value: " + rest, null); //$NON-NLS-1$
// Use an arbitrary other value so that the data structure
// remains consistent
kind = Kind.SOURCEPATH;
}
public String toEncodedString() {
return prefix + sep + kind.key;
}
public String toAbsolute(IJavaProject jproject) {
String out;
switch (kind) {
case ALL_SOURCE_FOLDERS:
try {
StringBuilder sb = new StringBuilder();
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
IClasspathEntry[] entries = jproject.getResolvedClasspath(true);
for (IClasspathEntry cpe : entries) {
if (cpe.getEntryKind() == IClasspathEntry.CPE_SOURCE) {
IResource r = root.getFolder(cpe.getPath());
IPath p = r.getLocation();
if (p != null) {
sb.append(p.toString());
sb.append(File.pathSeparator);
} else {
Log.errorlog("Failure to interpret an element of the Eclipse source folder list: " + cpe + Utils.space + cpe.getPath() + Utils.space + r, null); //$NON-NLS-1$
}
} else if (cpe.getEntryKind() == IClasspathEntry.CPE_PROJECT) {
IProject proj = (IProject) root.getProject(cpe.getPath()
.toString());
IJavaProject jp = JavaCore.create(proj);
String s = toAbsolute(jp);
sb.append(s);
sb.append(File.pathSeparator);
}
}
if (sb.length() > 0) sb.setLength(sb.length() - File.pathSeparator.length());
out = sb.toString();
} catch (JavaModelException e) {
Log.errorlog("Exception while interpreting source folders", e); //$NON-NLS-1$
out = Utils.emptyString;
}
break;
case CLASSPATH:
StringBuilder sb = new StringBuilder();
List<String> cp = Activator.utils().getClasspath(jproject);
for (String s : cp) {
sb.append(s);
sb.append(File.pathSeparator);
}
if (sb.length() > 0) sb.setLength(sb.length() - File.pathSeparator.length());
out = sb.toString();
break;
case SOURCEPATH:
out = getAbsolutePath(jproject,Env.sourceKey);
break;
case SYSTEM_SPECS:
// Choose between internal and external specs
out = Activator.utils().getInternalSystemSpecs();
break;
default:
Log.errorlog("An unexpected value of kind in SpecialPath: " + kind, null); //$NON-NLS-1$
out = Utils.emptyString;
}
return out;
}
public String display() {
return kind.description;
}
}
}