blob: 8aced6421eb8f4eac967b235b58f0e74b5ffb9a7 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2017 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
*
*******************************************************************************/
package org.eclipse.dltk.internal.core.builder;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
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.resources.IResourceDelta;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.resources.IncrementalProjectBuilder;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.Assert;
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.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.QualifiedName;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.dltk.core.DLTKContentTypeManager;
import org.eclipse.dltk.core.DLTKCore;
import org.eclipse.dltk.core.DLTKLanguageManager;
import org.eclipse.dltk.core.IBuildpathEntry;
import org.eclipse.dltk.core.IDLTKLanguageToolkit;
import org.eclipse.dltk.core.IModelMarker;
import org.eclipse.dltk.core.IScriptProjectFilenames;
import org.eclipse.dltk.core.ModelException;
import org.eclipse.dltk.core.ScriptProjectUtil;
import org.eclipse.dltk.core.builder.IBuildChange;
import org.eclipse.dltk.core.builder.IBuildState;
import org.eclipse.dltk.core.builder.IProjectChange;
import org.eclipse.dltk.core.builder.IScriptBuilder;
import org.eclipse.dltk.core.builder.IScriptBuilderVersionInfo;
import org.eclipse.dltk.core.environment.EnvironmentManager;
import org.eclipse.dltk.core.environment.IEnvironment;
import org.eclipse.dltk.internal.core.BuildpathEntry;
import org.eclipse.dltk.internal.core.ModelManager;
import org.eclipse.dltk.internal.core.ScriptProject;
import org.eclipse.osgi.util.NLS;
public class ScriptBuilder extends IncrementalProjectBuilder {
public static final boolean DEBUG = DLTKCore.DEBUG_SCRIPT_BUILDER;
public static final boolean TRACE = DLTKCore.TRACE_SCRIPT_BUILDER;
private static final int TRACE_BUILDER_MIN_ELAPSED_TIME = 10;
public IProject currentProject = null;
protected ScriptProject scriptProject = null;
State lastState;
/**
* Hook allowing to initialize some static state before a complete build
* iteration. This hook is invoked during PRE_AUTO_BUILD notification
*/
public static void buildStarting() {
// build is about to start
}
/**
* Hook allowing to reset some static state after a complete build
* iteration. This hook is invoked during POST_AUTO_BUILD notification
*/
public static void buildFinished() {
if (TRACE)
System.out.println("build finished"); //$NON-NLS-1$
}
private static void log(String message) {
System.out.println(message);
}
private static final QualifiedName PROPERTY_BUILDER_VERSION = new QualifiedName(
DLTKCore.PLUGIN_ID, "builderVersion"); //$NON-NLS-1$
private static final String CURRENT_VERSION = "200810012003-2123"; //$NON-NLS-1$
@Override
protected IProject[] build(int kind, @SuppressWarnings("rawtypes") Map args,
IProgressMonitor monitor) throws CoreException {
this.currentProject = getProject();
if (currentProject == null || !currentProject.isAccessible())
return new IProject[0];
if (!DLTKLanguageManager.hasScriptNature(this.currentProject)) {
return null;
}
long startTime = 0;
if (DEBUG || TRACE) {
startTime = System.currentTimeMillis();
log("\nStarting build of " + this.currentProject.getName() //$NON-NLS-1$
+ " @ " + new Date(startTime)); //$NON-NLS-1$
}
IProject[] requiredProjects = null;
try {
this.scriptProject = (ScriptProject) DLTKCore
.create(currentProject);
if (!ScriptProjectUtil.isBuilderEnabled(scriptProject)) {
if (monitor != null) {
monitor.done();
}
return null;
}
IEnvironment environment = EnvironmentManager
.getEnvironment(scriptProject);
if (environment == null || !environment.isConnected()) {
// Do not build if environment is not available.
// TODO: Store build requests and call builds when connection
// will
// be established.
if (monitor != null) {
monitor.done();
}
return null;
}
final String version = currentProject
.getPersistentProperty(PROPERTY_BUILDER_VERSION);
if (version == null) {
removeWrongTaskMarkers();
currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
CURRENT_VERSION);
kind = FULL_BUILD;
} else if (!CURRENT_VERSION.equals(version)) {
if ("200810012003".equals(version)) { //$NON-NLS-1$
removeWrongTaskMarkers();
}
currentProject.setPersistentProperty(PROPERTY_BUILDER_VERSION,
CURRENT_VERSION);
kind = FULL_BUILD;
}
if (monitor == null) {
monitor = new NullProgressMonitor();
}
if (kind == FULL_BUILD) {
if (DEBUG)
log("Performing full build as requested by user"); //$NON-NLS-1$
fullBuild(monitor);
} else {
if ((this.lastState = getLastState(currentProject,
monitor)) == null) {
if (DEBUG)
log("Performing full build since last saved state was not found"); //$NON-NLS-1$
fullBuild(monitor);
} else {
IResourceDelta delta = getDelta(getProject());
if (delta == null) {
if (DEBUG)
log("Performing full build since deltas are missing after incremental request"); //$NON-NLS-1$
fullBuild(monitor);
} else if (isProjectConfigChange(delta)) {
if (DEBUG)
log("Performing full build since .project/.buildpath change"); //$NON-NLS-1$
fullBuild(monitor);
} else {
if (DEBUG)
log("Performing incremental build"); //$NON-NLS-1$
requiredProjects = getRequiredProjects(true);
incrementalBuild(delta, requiredProjects, monitor);
}
}
}
} catch (OperationCanceledException e) {
// TODO what?
} finally {
cleanup();
}
if (DEBUG || TRACE) {
final long endTime = System.currentTimeMillis();
if (DEBUG) {
log("Finished build of " + currentProject.getName() //$NON-NLS-1$
+ " @ " + new Date(endTime) + ", elapsed " //$NON-NLS-1$ //$NON-NLS-2$
+ (endTime - startTime) + " ms"); //$NON-NLS-1$
}
if (TRACE) {
System.out.println(
"-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$
System.out.println("Finished build of project:" //$NON-NLS-1$
+ currentProject.getName() + "\n" //$NON-NLS-1$
+ "Building time:" //$NON-NLS-1$
+ Long.toString(endTime - startTime) + "\n" //$NON-NLS-1$
+ "Build type:" //$NON-NLS-1$
+ (kind == FULL_BUILD ? "Full build" //$NON-NLS-1$
: "Incremental build")); //$NON-NLS-1$
System.out.println(
"-----------------------------------------------------------------"); //$NON-NLS-1$
}
}
monitor.done();
if (requiredProjects == null) {
requiredProjects = getRequiredProjects(true);
}
return requiredProjects;
}
private void cleanup() {
lastState = null;
}
private static boolean isProjectConfigChange(IResourceDelta projectDelta) {
final String[] filenames = { IScriptProjectFilenames.BUILDPATH_FILENAME,
IScriptProjectFilenames.PROJECT_FILENAME };
for (String filename : filenames) {
final IResourceDelta delta = projectDelta
.findMember(new Path(filename));
if (delta != null) {
switch (delta.getKind()) {
case IResourceDelta.ADDED:
case IResourceDelta.REMOVED:
return true;
case IResourceDelta.CHANGED:
return (delta.getFlags() & (IResourceDelta.CONTENT
| IResourceDelta.ENCODING)) != 0;
}
}
}
return false;
}
/**
* Remove incorrect task markers.
*
* DLTK 0.95 were creating wrong task markers, so this function is here to
* remove them. New markers will be created by the builder later.
*
* @param project
* @throws CoreException
*/
private void removeWrongTaskMarkers() throws CoreException {
final IMarker[] markers = currentProject.findMarkers(IMarker.TASK,
false, IResource.DEPTH_INFINITE);
for (int i = 0; i < markers.length; ++i) {
final IMarker marker = markers[i];
final IResource resource = marker.getResource();
if (resource.getType() != IResource.FILE) {
continue;
}
if (!DLTKContentTypeManager.isValidResourceForContentType(
scriptProject.getLanguageToolkit(), resource)) {
continue;
}
final Map<?, ?> attributes = marker.getAttributes();
if (attributes == null) {
continue;
}
if (!Boolean.FALSE.equals(attributes.get(IMarker.USER_EDITABLE))) {
continue;
}
if (attributes.containsKey(IMarker.LINE_NUMBER)
&& attributes.containsKey(IMarker.MESSAGE)
&& attributes.containsKey(IMarker.PRIORITY)
&& attributes.containsKey(IMarker.CHAR_START)
&& attributes.containsKey(IMarker.CHAR_END)) {
marker.delete();
}
}
}
private static class BuildState extends AbstractBuildState {
final State state;
public BuildState(State state) {
super(state.scriptProjectName);
this.state = state;
}
@Override
public void recordImportProblem(IPath path) {
this.state.recordImportProblem(path);
}
@Override
public void recordDependency(IPath path, IPath dependency, int flags) {
Assert.isTrue(flags != 0);
this.state.recordDependency(path, dependency, flags);
}
}
@Override
protected void clean(IProgressMonitor monitor) throws CoreException {
long start = 0;
if (TRACE) {
start = System.currentTimeMillis();
}
this.currentProject = getProject();
if (!DLTKLanguageManager.hasScriptNature(this.currentProject)) {
return;
}
this.scriptProject = (ScriptProject) DLTKCore.create(currentProject);
if (currentProject == null || !currentProject.isAccessible())
return;
try {
monitor.beginTask(NLS.bind(Messages.ScriptBuilder_cleaningScriptsIn,
currentProject.getName()), 66);
if (monitor.isCanceled()) {
return;
}
ModelManager.getModelManager().setLastBuiltState(currentProject,
null);
IScriptBuilder[] builders = getScriptBuilders();
if (builders != null) {
for (int k = 0; k < builders.length; k++) {
IProgressMonitor sub = new SubProgressMonitor(monitor, 1);
builders[k].clean(scriptProject, sub);
if (monitor.isCanceled()) {
break;
}
}
}
resetBuilders(builders,
new BuildStateStub(currentProject.getName()), monitor);
} catch (CoreException e) {
if (DLTKCore.DEBUG) {
e.printStackTrace();
}
}
if (TRACE) {
System.out.println(
"-----SCRIPT-BUILDER-INFORMATION-TRACE----------------------------"); //$NON-NLS-1$
System.out.println("Finished clean of project:" //$NON-NLS-1$
+ currentProject.getName() + "\n" //$NON-NLS-1$
+ "Building time:" //$NON-NLS-1$
+ Long.toString(System.currentTimeMillis() - start));
System.out.println(
"-----------------------------------------------------------------"); //$NON-NLS-1$
}
monitor.done();
}
private IProject[] getRequiredProjects(boolean includeBinaryPrerequisites) {
if (scriptProject == null)
return new IProject[0];
IWorkspaceRoot workspaceRoot = ResourcesPlugin.getWorkspace().getRoot();
ArrayList<IProject> projects = new ArrayList<>();
try {
IBuildpathEntry[] entries = scriptProject.getExpandedBuildpath();
for (int i = 0, l = entries.length; i < l; i++) {
IBuildpathEntry entry = entries[i];
IPath path = entry.getPath();
IProject p = null;
switch (entry.getEntryKind()) {
case IBuildpathEntry.BPE_PROJECT:
p = workspaceRoot.getProject(path.lastSegment());
// missing projects are considered too
if (((BuildpathEntry) entry).isOptional()
&& !ScriptProject.hasScriptNature(p))
// except if entry is optional
p = null;
break;
case IBuildpathEntry.BPE_LIBRARY:
if (includeBinaryPrerequisites && path.segmentCount() > 1) {
/*
* some binary resources on the class path can come from
* projects that are not included in the project
* references
*/
IResource resource = workspaceRoot
.findMember(path.segment(0));
if (resource instanceof IProject)
p = (IProject) resource;
}
}
if (p != null && !projects.contains(p))
projects.add(p);
}
} catch (ModelException e) {
return new IProject[0];
}
return projects.toArray(new IProject[projects.size()]);
}
public State getLastState(IProject project, IProgressMonitor monitor) {
return (State) ModelManager.getModelManager().getLastBuiltState(project,
monitor);
}
private State clearLastState() {
State state = new State(this);
State prevState = (State) ModelManager.getModelManager()
.getLastBuiltState(currentProject, null);
if (prevState != null) {
if (prevState.noCleanExternalFolders) {
state.externalFolderLocations = prevState.externalFolderLocations;
return state;
}
}
ModelManager.getModelManager().setLastBuiltState(currentProject, null);
return state;
}
private static final int WORK_RESOURCES = 50;
private static final int WORK_EXTERNAL = 100;
private static final int WORK_SOURCES = 100;
private static final int WORK_BUILD = 750;
protected static final String NONAME = ""; //$NON-NLS-1$
protected void fullBuild(final IProgressMonitor monitor) {
final State newState = clearLastState();
final IBuildState buildState = new BuildState(newState);
IScriptBuilder[] builders = null;
try {
monitor.setTaskName(
NLS.bind(Messages.ScriptBuilder_buildingScriptsIn,
currentProject.getName()));
monitor.beginTask(NONAME,
WORK_RESOURCES + WORK_EXTERNAL + WORK_SOURCES + WORK_BUILD);
builders = getScriptBuilders();
if (builders == null || builders.length == 0) {
return;
}
final IBuildChange buildChange = new FullBuildChange(currentProject,
monitor);
for (IScriptBuilder builder : builders) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
builder.prepare(buildChange, buildState, monitor);
}
for (IScriptBuilder builder : builders) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
final long start = TRACE ? System.currentTimeMillis() : 0;
builder.build(buildChange, buildState, monitor);
if (TRACE) {
final long elapsed = System.currentTimeMillis() - start;
if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) {
System.out.println(builder.getClass().getName() + " "
+ elapsed + "ms");
}
}
}
saveBuilderVersions(builders);
updateExternalFolderLocations(newState, buildChange);
} catch (CoreException e) {
DLTKCore.error(e);
} finally {
resetBuilders(builders, buildState, monitor);
ModelManager.getModelManager().setLastBuiltState(currentProject,
newState);
monitor.done();
this.lastState = null;
}
}
protected void resetBuilders(IScriptBuilder[] builders, IBuildState state,
IProgressMonitor monitor) {
if (builders != null) {
for (int k = 0; k < builders.length; k++) {
builders[k].endBuild(scriptProject, state, monitor);
}
}
}
private static class DependencyBuildChange extends BuildChange {
public DependencyBuildChange(IProject project, IResourceDelta delta,
List<IFile> files, IProgressMonitor monitor) {
super(project, delta, files, monitor);
}
@Override
public boolean isDependencyBuild() {
return true;
}
}
protected void incrementalBuild(IResourceDelta delta,
IProject[] requiredProjects, IProgressMonitor monitor)
throws CoreException {
final State newState = new State(this);
newState.copyFrom(this.lastState);
final Set<IPath> externalFoldersBefore = new HashSet<>(
newState.getExternalFolders());
final BuildState buildState = new BuildState(newState);
IScriptBuilder[] builders = null;
try {
monitor.setTaskName(
NLS.bind(Messages.ScriptBuilder_buildingScriptsIn,
currentProject.getName()));
monitor.beginTask(NONAME,
WORK_RESOURCES + WORK_EXTERNAL + WORK_SOURCES + WORK_BUILD);
builders = getScriptBuilders();
if (builders == null || builders.length == 0) {
return;
}
IBuildChange buildChange = null;
if (isBuilderVersionChange(builders)) {
buildChange = new FullBuildChange(currentProject, monitor);
newState.resetDependencies();
}
if (buildChange == null) {
buildChange = createBuildChange(delta, requiredProjects,
externalFoldersBefore, monitor);
}
for (IScriptBuilder builder : builders) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
builder.prepare(buildChange, buildState, monitor);
if (buildChange.getBuildType() == IScriptBuilder.FULL_BUILD
&& buildChange instanceof IncrementalBuildChange) {
if (TRACE) {
System.out.println("Full build requested by "
+ builder.getClass().getName());
}
buildChange = new FullBuildChange(currentProject, monitor);
newState.resetDependencies();
}
}
if (buildChange instanceof IncrementalBuildChange) {
final Set<IPath> changes = ((IncrementalBuildChange) buildChange)
.getChangedPaths();
if (TRACE) {
System.out.println(" Changes: " + changes);
}
newState.removeDependenciesFor(changes);
}
for (IScriptBuilder builder : builders) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
final long start = TRACE ? System.currentTimeMillis() : 0;
builder.build(buildChange, buildState, monitor);
if (TRACE) {
final long elapsed = System.currentTimeMillis() - start;
if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) {
System.out.println(builder.getClass().getName() + " "
+ elapsed + "ms");
}
}
}
newState.recordStructuralChanges(buildState.getStructuralChanges());
if (buildChange instanceof IncrementalBuildChange) {
final Set<IPath> processed = new HashSet<>();
final Set<IPath> changes = ((IncrementalBuildChange) buildChange)
.getChangedPaths();
processed.addAll(changes);
final Set<IPath> queue = new HashSet<>();
final Set<IPath> newStructuralChanges = new HashSet<>();
// TODO review cross-project dependency handling
for (IProjectChange projectChange : buildChange
.getRequiredProjectChanges()) {
final Collection<IPath> projectChanges = ((IncrementalProjectChange) projectChange)
.getChangedPaths();
final State projectState = getLastState(
projectChange.getProject(), monitor);
if (projectState != null) {
projectChanges.addAll(projectState
.getAllStructuralDependencies(projectChanges));
}
this.lastState.findDependenciesOf(projectChanges,
buildState.getStructuralChanges(), false, queue,
newStructuralChanges);
}
final IWorkspaceRoot root = ResourcesPlugin.getWorkspace()
.getRoot();
buildState.recordStructuralChanges(
((IncrementalProjectChange) buildChange)
.getAddedPaths());
buildState.recordStructuralChanges(
((IncrementalProjectChange) buildChange)
.getDeletedPaths());
for (int iterationNumber = 0;; ++iterationNumber) {
this.lastState.findDependenciesOf(changes,
buildState.getStructuralChanges(),
iterationNumber == 0, queue, newStructuralChanges);
queue.removeAll(processed);
if (queue.isEmpty()) {
break;
}
if (TRACE) {
System.out.println(" Queue: " + queue);
}
newState.removeDependenciesFor(queue);
final List<IFile> files = new ArrayList<>();
for (IPath path : queue) {
files.add(root.getFile(path));
}
buildState.resetStructuralChanges();
buildState.recordStructuralChanges(newStructuralChanges);
newStructuralChanges.clear();
final DependencyBuildChange qChange = new DependencyBuildChange(
currentProject, delta, files, monitor);
for (IScriptBuilder builder : builders) {
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
final long start = TRACE ? System.currentTimeMillis()
: 0;
builder.build(qChange, buildState, monitor);
if (TRACE) {
final long elapsed = System.currentTimeMillis()
- start;
if (elapsed > TRACE_BUILDER_MIN_ELAPSED_TIME) {
System.out.println(builder.getClass().getName()
+ " " + elapsed + "ms");
}
}
}
changes.clear();
changes.addAll(queue);
processed.addAll(queue);
queue.clear();
}
}
saveBuilderVersions(builders);
updateExternalFolderLocations(newState, buildChange);
} catch (CoreException e) {
DLTKCore.error(e);
} finally {
resetBuilders(builders, buildState, monitor);
if (TRACE) {
// newState.dumpDependencies();
}
ModelManager.getModelManager().setLastBuiltState(currentProject,
newState);
monitor.done();
this.lastState = null;
}
}
private QualifiedName getQualifiedName(IScriptBuilderVersionInfo vi) {
return vi.getVersionKey();
}
protected boolean isBuilderVersionChange(IScriptBuilder[] builders)
throws CoreException {
for (IScriptBuilder builder : builders) {
if (builder instanceof IScriptBuilderVersionInfo) {
final IScriptBuilderVersionInfo vi = (IScriptBuilderVersionInfo) builder;
final String builderVersion = vi.getVersion();
if (builderVersion == null)
continue;
final String version = currentProject
.getPersistentProperty(getQualifiedName(vi));
if (!builderVersion.equals(version)) {
return true;
}
}
}
return false;
}
private void saveBuilderVersions(IScriptBuilder[] builders)
throws CoreException {
for (IScriptBuilder builder : builders) {
if (builder instanceof IScriptBuilderVersionInfo) {
final IScriptBuilderVersionInfo vi = (IScriptBuilderVersionInfo) builder;
final String builderVersion = vi.getVersion();
if (builderVersion == null)
continue;
currentProject.setPersistentProperty(getQualifiedName(vi),
builderVersion);
}
}
}
private IBuildChange createBuildChange(IResourceDelta delta,
IProject[] requiredProjects, final Set<IPath> externalFoldersBefore,
IProgressMonitor monitor) {
final List<IProjectChange> projectChanges = new ArrayList<>();
for (IProject project : requiredProjects) {
final IResourceDelta projectDelta = getDelta(project);
if (projectDelta == null) {
return new FullBuildChange(currentProject, monitor);
}
if (projectDelta.getKind() != IResourceDelta.NO_CHANGE) {
projectChanges.add(new IncrementalProjectChange(projectDelta,
project, monitor));
}
}
return new IncrementalBuildChange(delta,
projectChanges
.toArray(new IProjectChange[projectChanges.size()]),
currentProject, monitor,
new ArrayList<>(externalFoldersBefore));
}
private static void updateExternalFolderLocations(State state,
IBuildChange buildChange) throws CoreException {
state.externalFolderLocations.clear();
state.externalFolderLocations
.addAll(buildChange.getExternalPaths(IProjectChange.DEFAULT));
}
/**
* Return script builders for the current project. ScriptBuilders are
* initialized here so this method should be called only once during build
* operation.
*
* @return
* @throws CoreException
*/
protected IScriptBuilder[] getScriptBuilders() throws CoreException {
IDLTKLanguageToolkit toolkit = DLTKLanguageManager
.getLanguageToolkit(scriptProject);
if (toolkit != null) {
IScriptBuilder[] builders = getScriptBuilders(toolkit);
if (builders != null) {
final List<IScriptBuilder> result = new ArrayList<>(
builders.length);
for (int k = 0; k < builders.length; k++) {
if (builders[k].initialize(scriptProject)) {
result.add(builders[k]);
}
}
builders = result.toArray(new IScriptBuilder[result.size()]);
}
return builders;
} else {
return null;
}
}
protected IScriptBuilder[] getScriptBuilders(IDLTKLanguageToolkit toolkit) {
return ScriptBuilderManager.getScriptBuilders(toolkit.getNatureId());
}
public static void removeProblemsAndTasksFor(IResource resource) {
try {
if (resource != null && resource.exists()) {
resource.deleteMarkers(IModelMarker.SCRIPT_MODEL_PROBLEM_MARKER,
false, IResource.DEPTH_INFINITE);
resource.deleteMarkers(IModelMarker.TASK_MARKER, false,
IResource.DEPTH_INFINITE);
// delete managed markers
}
} catch (CoreException e) {
// assume there were no problems
}
}
public static void writeState(Object state, DataOutputStream out)
throws IOException {
((State) state).write(out);
}
public static State readState(IProject project, DataInputStream in)
throws IOException {
State state = State.read(project, in);
return state;
}
}