blob: 40c0b6400a44589ec489407475794d18567578b1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2008 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.eclipse.wst.validation.internal;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.resources.ISaveContext;
import org.eclipse.core.resources.ISaveParticipant;
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.osgi.util.NLS;
import org.eclipse.wst.validation.DependentResource;
import org.eclipse.wst.validation.IDependencyIndex;
import org.eclipse.wst.validation.Validator;
import org.eclipse.wst.validation.internal.plugin.ValidationPlugin;
/**
* A simple implementation of the IDependencyIndex. This will probably be
* replaced with a higher performance, more robust index, at some point in the
* future.
* <p>
* The format of the index is:
*
* <pre>
* Version number
* Number of depends on entries
* depends on file name
* number of dependent entries
* dependent file name
* number of validators
* validator id
* </pre>
*
* @author karasiuk
*/
public class DependencyIndex implements IDependencyIndex, ISaveParticipant {
/**
* An index so that we can determine which things depend on this resource.
*/
private Map<IResource,Set<Depends>> _dependsOn;
/**
* An index so that we can determine who the resource depends on.
*/
private Map<IResource,Set<Depends>> _dependents;
private boolean _dirty;
private static IResource[] EmptyResources = new IResource[0];
/** Version of the persistent index. */
private static final int CurrentVersion = 1;
public synchronized void add(String id, IResource dependent, IResource dependsOn) {
init();
if (dependsOn == null || dependent == null)return;
Depends d = getOrCreateDepends(dependent, dependsOn);
if (d.getValidators().add(id))_dirty = true;
}
private Depends getOrCreateDepends(IResource dependent, IResource dependsOn) {
Set<Depends> set = getSet(_dependents, dependent);
for (Depends d : set){
if (d.getDependsOn() != null && d.getDependsOn().equals(dependsOn)) return d;
}
Depends d = new Depends(dependent, dependsOn);
_dirty = true;
set.add(d);
getSet(_dependsOn, dependsOn).add(d);
return d;
}
/**
* Answer the set for the resource, creating it if you need to.
*/
private Set<Depends> getSet(Map<IResource, Set<Depends>> map, IResource resource) {
Set<Depends> set = map.get(resource);
if (set == null){
set = new HashSet<Depends>(5);
map.put(resource, set);
}
return set;
}
/**
* Restore the dependency index. See the class comment for the structure.
*/
private void init() {
if (_dependsOn != null)return;
boolean error = false;
File f = getIndexLocation();
if (!f.exists()){
_dependsOn = new HashMap<IResource,Set<Depends>>(100);
_dependents = new HashMap<IResource,Set<Depends>>(100);
}
else {
String errorMessage = ValMessages.Error21;
DataInputStream in = null;
try {
IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
in = new DataInputStream(new FileInputStream(f));
int version = in.readInt();
if (version != CurrentVersion){
error = true;
String msg = NLS.bind(ValMessages.ErrDependencyVersion, CurrentVersion);
throw new IllegalStateException(msg);
}
int numDependsOn = in.readInt();
_dependsOn = new HashMap<IResource,Set<Depends>>(numDependsOn+100);
_dependents = new HashMap<IResource,Set<Depends>>(numDependsOn+100);
for (int i=0; i<numDependsOn; i++){
String v = in.readUTF();
IResource dependsOn = root.findMember(v);
if (dependsOn == null){
Tracing.log(NLS.bind(errorMessage, v));
}
int numDependents = in.readInt();
for (int j=0; j<numDependents; j++){
v = in.readUTF();
IResource dependent = root.findMember(v);
if (dependent == null){
Tracing.log(NLS.bind(errorMessage, v));
}
int numVal = in.readInt();
for (int k=0; k<numVal; k++){
String id = in.readUTF();
if (dependent != null && dependsOn != null)add(id, dependent, dependsOn);
}
}
}
}
catch (IOException e){
error = true;
ValidationPlugin.getPlugin().handleException(e);
}
finally {
Misc.close(in);
if (error){
_dependsOn = new HashMap<IResource,Set<Depends>>(100);
_dependents = new HashMap<IResource,Set<Depends>>(100);
f.delete();
}
}
}
}
public synchronized void clear(IProject project) {
init();
for (Map.Entry<IResource,Set<Depends>> me : _dependents.entrySet()){
if (me.getKey().getProject() == project){
for (Depends d : me.getValue()){
if (d.delete())_dirty = true;
}
}
}
}
public synchronized IResource[] get(String validatorId, IResource dependsOn) {
init();
List<IResource> list = new LinkedList<IResource>();
Set<Depends> set = getSet(_dependsOn, dependsOn);
for (Depends d : set){
for (String id : d.getValidators()){
if (validatorId.equals(id))list.add(d.getDependent());
}
}
if (list.size() == 0)return EmptyResources;
IResource[] resources = new IResource[list.size()];
list.toArray(resources);
return resources;
}
public synchronized List<DependentResource> get(IResource dependsOn) {
init();
List<DependentResource> list = new LinkedList<DependentResource>();
Set<Depends> set = getSet(_dependsOn, dependsOn);
ValManager vm = ValManager.getDefault();
for (Depends d : set){
for (String id : d.getValidators()){
Validator v = vm.getValidator(id, d.getDependent().getProject());
if (v != null)list.add(new DependentResource(d.getDependent(), v));
}
}
return list;
}
public synchronized void set(String id, IResource dependent, IResource[] dependsOn) {
init();
Set<Depends> set = getSet(_dependents, dependent);
for (Depends d : set){
if (d.delete(id))_dirty = true;
}
if (dependsOn != null){
for (IResource d : dependsOn)add(id, dependent, d);
}
}
public boolean isDependedOn(IResource resource) {
init();
Set<Depends> set = _dependsOn.get(resource);
if (set == null || set.size() == 0)return false;
return true;
}
public void doneSaving(ISaveContext context) {
}
public void prepareToSave(ISaveContext context) throws CoreException {
}
public void rollback(ISaveContext context) {
}
/**
* Persist the dependency index. See the class comment for the structure.
*/
public synchronized void saving(ISaveContext context) throws CoreException {
if (!_dirty)return;
_dirty = false;
DataOutputStream out = null;
try {
File f = getIndexLocation();
out = new DataOutputStream(new FileOutputStream(f));
out.writeInt(CurrentVersion);
Map<String, Set<DependsResolved>> map = compress(_dependsOn);
out.writeInt(map.size());
for (Map.Entry<String, Set<DependsResolved>> me : map.entrySet()){
out.writeUTF(me.getKey());
Set<DependsResolved> set = me.getValue();
out.writeInt(set.size());
for (DependsResolved d : set){
out.writeUTF(d.resource);
out.writeInt(d.validators.size());
for (String id : d.validators){
out.writeUTF(id);
}
}
}
}
catch (IOException e){
ValidationPlugin.getPlugin().handleException(e);
}
finally {
Misc.close(out);
}
}
private Map<String, Set<DependsResolved>> compress(Map<IResource, Set<Depends>> dependsOn) {
Map<String, Set<DependsResolved>> map = new HashMap<String, Set<DependsResolved>>(dependsOn.size());
for (Map.Entry<IResource, Set<Depends>> me : dependsOn.entrySet()){
Set<DependsResolved> set = new HashSet<DependsResolved>(me.getValue().size());
for (Depends d : me.getValue()){
IPath path = d.getDependent().getFullPath();
if (path != null){
DependsResolved dr = new DependsResolved();
dr.resource = path.toPortableString();
if (d.getValidators().size() > 0){
dr.validators = d.getValidators();
set.add(dr);
}
}
}
if (set.size() > 0){
IResource res = me.getKey();
if (res != null){
IPath path = res.getFullPath();
if (path != null)map.put(path.toPortableString(), set);
}
}
}
return map;
}
private File getIndexLocation() {
IPath path = ValidationPlugin.getPlugin().getStateLocation().append("dep.index"); //$NON-NLS-1$
return path.toFile();
}
/**
* Keep track of a relationship between a dependent and the thing that it
* depends on.
*
* @author karasiuk
*
*/
private static class Depends {
/** The resource that is being depended on, for example a.xsd */
private IResource _dependsOn;
/** The resource that is dependent, for example a.xml */
private IResource _dependent;
/** The id's of the validators that have asserted the dependency. */
private Set<String> _validators;
public Depends(IResource dependent, IResource dependsOn) {
_dependent = dependent;
_dependsOn = dependsOn;
_validators = new HashSet<String>(5);
}
/**
* Answer true if the id was deleted.
*/
public boolean delete(String id) {
return _validators.remove(id);
}
/**
* Delete all the dependency assertions for all of your validators.
* @return false if there was nothing to delete
*/
public boolean delete() {
boolean deleted = _validators.size() > 0;
if (deleted)_validators.clear();
return deleted;
}
public IResource getDependsOn() {
return _dependsOn;
}
public IResource getDependent() {
return _dependent;
}
public Set<String> getValidators() {
return _validators;
}
}
private static class DependsResolved {
String resource;
Set<String> validators;
}
}