/*******************************************************************************
* Copyright (c) 2009 Red Hat, Inc.
* Distributed under license by Red Hat, Inc. All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.jboss.tools.common.validation.internal;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.Path;
import org.jboss.tools.common.el.core.ELReference;
import org.jboss.tools.common.util.UniquePaths;
import org.jboss.tools.common.validation.ValidationMessages;
import org.jboss.tools.common.xml.XMLUtilities;
import org.w3c.dom.Element;
/**
* @author Alexey Kazakov
*/
public class LinkCollection {
protected Map<String, Set<IPath>> resourcesByVariableName = new HashMap<String, Set<IPath>>();
protected Map<IPath, Set<String>> variableNamesByResource = new HashMap<IPath, Set<String>>();
protected Map<String, Set<IPath>> resourcesByDeclaringVariableName = new HashMap<String, Set<IPath>>();
protected Map<IPath, Set<String>> declaringVariableNamesByResource = new HashMap<IPath, Set<String>>();
protected Set<IPath> unnamedResources = new HashSet<IPath>();
private String id;
public LinkCollection(String id) {
this.id = id;
}
public void disableResourcesByVariableName() {
resourcesByVariableName = null;
}
protected int modifications = 0;
/**
* Save link between resource and variable name.
* It's needed for incremental validation because we must save all linked resources of changed java file.
*/
public void addLinkedResource(String variableName, IPath linkedResourcePath, boolean declaration) {
if(linkedResourcePath==null) {
throw new IllegalArgumentException(ValidationMessages.VALIDATION_CONTEXT_LINKED_RESOURCE_PATH_MUST_NOT_BE_NULL);
}
if(variableName==null) {
throw new IllegalArgumentException(ValidationMessages.VALIDATION_CONTEXT_VARIABLE_NAME_MUST_NOT_BE_NULL);
}
linkedResourcePath = UniquePaths.getInstance().intern(linkedResourcePath);
if(resourcesByVariableName != null) {
synchronized(this) {
Set<IPath> linkedResources = resourcesByVariableName.get(variableName);
if(linkedResources==null) {
// create set of linked resources with variable name.
linkedResources = new HashSet<IPath>();
resourcesByVariableName.put(variableName, linkedResources);
}
//save linked resources.
if(linkedResources.add(linkedResourcePath)) {
modifications++;
}
}
}
Set<String> variableNames = null;
// Save link between resource and variable names. It's needed if variable name changes in resource file.
synchronized(this) {
variableNames = variableNamesByResource.get(linkedResourcePath);
if(variableNames==null) {
variableNames = new HashSet<String>();
variableNamesByResource.put(linkedResourcePath, variableNames);
}
if(variableNames.add(variableName.intern())) {
modifications++;
}
}
if(declaration) {
synchronized(this) {
Set<IPath> linkedResources = resourcesByDeclaringVariableName.get(variableName);
if(linkedResources==null) {
// create set of linked resources with declaring variable name.
linkedResources = new HashSet<IPath>();
resourcesByDeclaringVariableName.put(variableName, linkedResources);
}
// save linked resources.
if(linkedResources.add(linkedResourcePath)) {
modifications++;
}
}
// Save link between resource and declaring variable names. It's needed if variable name changes in resource file.
variableNames = declaringVariableNamesByResource.get(linkedResourcePath);
if(variableNames==null) {
variableNames = new HashSet<String>();
declaringVariableNamesByResource.put(linkedResourcePath, variableNames);
}
if(variableNames.add(variableName)) {
modifications++;
}
}
}
/**
* Removes link between resource and variable name.
* @param oldVariableName
* @param linkedResourcePath
*/
public void removeLinkedResource(String name, IPath linkedResourcePath) {
if(resourcesByVariableName != null) {
synchronized(this) {
Set<IPath> linkedResources = resourcesByVariableName.get(name);
if(linkedResources!=null) {
// remove linked resource.
if(linkedResources.remove(linkedResourcePath)) {
modifications++;
}
}
if(linkedResources.isEmpty()) {
resourcesByVariableName.remove(name);
}
}
}
// Remove link between resource and declaring variable names.
Set<String> variableNames = variableNamesByResource.get(linkedResourcePath);
if(variableNames!=null) {
if(variableNames.remove(name)) {
modifications++;
}
}
if(variableNames.isEmpty()) {
variableNamesByResource.remove(linkedResourcePath);
}
synchronized(this) {
Set<IPath> linkedResources = resourcesByDeclaringVariableName.get(name);
if(linkedResources!=null) {
// remove linked resource.
if(linkedResources.remove(linkedResourcePath)) {
modifications++;
}
}
if(linkedResources.isEmpty()) {
resourcesByDeclaringVariableName.remove(name);
}
}
// Remove link between resource and declaring variable names.
variableNames = declaringVariableNamesByResource.get(linkedResourcePath);
if(variableNames!=null) {
if(variableNames.remove(name)) {
modifications++;
}
}
if(variableNames.isEmpty()) {
declaringVariableNamesByResource.remove(linkedResourcePath);
}
}
/**
* Removes link between resources and variable names.
* @param linkedResources
*/
public void removeLinkedResources(Set<IPath> resources) {
for (IPath resource : resources) {
removeLinkedResource(resource);
}
}
/**
* Removes link between resource and variable names.
* @param linkedResources
*/
public synchronized void removeLinkedResource(IPath resource) {
Set<String> resourceNames = variableNamesByResource.get(resource);
if(resourceNames!=null && resourcesByVariableName != null) {
for (String name : resourceNames) {
Set<IPath> linkedResources = resourcesByVariableName.get(name);
if(linkedResources!=null) {
if(linkedResources.remove(resource)) {
modifications++;
}
if(linkedResources.isEmpty()) {
resourcesByVariableName.remove(name);
}
}
}
}
if(variableNamesByResource.remove(resource) != null) {
modifications++;
}
resourceNames = declaringVariableNamesByResource.get(resource);
if(resourceNames!=null) {
for (String name : resourceNames) {
Set<IPath> linkedResources = resourcesByDeclaringVariableName.get(name);
if(linkedResources!=null) {
if(linkedResources.remove(resource)) {
modifications++;
}
if(linkedResources.isEmpty()) {
resourcesByDeclaringVariableName.remove(name);
}
}
}
}
if(declaringVariableNamesByResource.remove(resource) != null) {
modifications++;
}
}
public Set<IPath> getResourcesByVariableName(String variableName, boolean declaration) {
if(!declaration && resourcesByVariableName == null) {
throw new RuntimeException("ResourcesByVariableName are disabled.");
}
return declaration ? resourcesByDeclaringVariableName.get(variableName) : resourcesByVariableName.get(variableName);
}
public synchronized Set<String> getVariableNamesByResource(IPath fullPath, boolean declaration) {
return declaration?declaringVariableNamesByResource.get(fullPath):variableNamesByResource.get(fullPath);
}
/**
* Adds resource without any link to any context variable name.
* @param fullPath
*/
public void addUnnamedResource(IPath fullPath) {
if(unnamedResources.add(fullPath)) {
modifications++;
}
}
/**
* @return Set of resources without any link to any context variable name.
* @param fullPath
*/
public Set<IPath> getUnnamedResources() {
return unnamedResources;
}
/**
* Removes unnamed resource.
* @param fullPath
*/
public void removeUnnamedResource(IPath fullPath) {
if(unnamedResources.remove(fullPath)) {
modifications++;
}
}
/**
* Clear all references
*/
public synchronized void clearAll() {
if(resourcesByVariableName != null) {
resourcesByVariableName.clear();
}
variableNamesByResource.clear();
declaringVariableNamesByResource.clear();
resourcesByDeclaringVariableName.clear();
unnamedResources.clear();
modifications = 0;
}
/**
* Store the collection to XML
* @param root
*/
public synchronized void store(Element root, Map<String, String> pathAliases) {
Set<IPath> paths = variableNamesByResource.keySet();
for (IPath path: paths) {
String pathAlias = ELReference.getAlias(pathAliases, path.toString());
Set<String> variables = variableNamesByResource.get(path);
if(variables == null || variables.isEmpty()) continue;
StringBuilder declarationFalseNames = new StringBuilder();
StringBuilder declarationTrueNames = new StringBuilder();
for (String name: variables) {
String nameAlias = ELReference.getAlias(pathAliases, name);
if(checkDeclaration(path, name)) {
declarationTrueNames.append(nameAlias).append(";");
} else {
declarationFalseNames.append(nameAlias).append(";");
}
}
if(declarationFalseNames.length() > 0) {
Element linkedResource = XMLUtilities.createElement(root, "linked-resource"); //$NON-NLS-1$
linkedResource.setAttribute("path", pathAlias); //$NON-NLS-1$
linkedResource.setAttribute("name", declarationFalseNames.toString()); //$NON-NLS-1$
}
if(declarationTrueNames.length() > 0) {
Element linkedResource = XMLUtilities.createElement(root, "linked-resource"); //$NON-NLS-1$
linkedResource.setAttribute("path", pathAlias); //$NON-NLS-1$
linkedResource.setAttribute("name", declarationTrueNames.toString()); //$NON-NLS-1$
linkedResource.setAttribute("declaration", "true"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
StringBuilder unnamedPaths = new StringBuilder();
for (IPath unnamedPath: unnamedResources) {
String pathAlias = ELReference.getAlias(pathAliases, unnamedPath.toString());
unnamedPaths.append(pathAlias).append(";");
}
if(unnamedPaths.length() > 0) {
Element unnamedPathElement = XMLUtilities.createElement(root, "unnamed-path"); //$NON-NLS-1$
unnamedPathElement.setAttribute("path", ELReference.getAlias(pathAliases, unnamedPaths.toString())); //$NON-NLS-1$
}
modifications = 0;
}
/**
* Load the collection from XML
* @param root
*/
public void load(Element root, Map<String, String> pathAliases) {
if(root == null) return;
Element[] linkedResources = XMLUtilities.getChildren(root, "linked-resource"); //$NON-NLS-1$
if(linkedResources != null) for (int i = 0; i < linkedResources.length; i++) {
String path = linkedResources[i].getAttribute("path"); //$NON-NLS-1$
if(path == null || path.trim().length() == 0) continue;
if(path.indexOf(';') > 0) {
//support to old format
path = path.substring(0, path.indexOf(';'));
}
path = ELReference.getPath(pathAliases, path);
IPath pathObject = new Path(path);
String name1 = linkedResources[i].getAttribute("name"); //$NON-NLS-1$
if(name1 == null || name1.trim().length() == 0) continue;
String declaration = linkedResources[i].getAttribute("declaration"); //$NON-NLS-1$
boolean declarationFlag = "true".equals(declaration); //$NON-NLS-1$
String[] names = name1.split(";");
for (String name: names) {
name = ELReference.getPath(pathAliases, name);
addLinkedResource(name, pathObject, declarationFlag);
}
}
Element[] unnamedPathElement = XMLUtilities.getChildren(root, "unnamed-path"); //$NON-NLS-1$
if(unnamedPathElement != null) for (int i = 0; i < unnamedPathElement.length; i++) {
String path1 = unnamedPathElement[i].getAttribute("path"); //$NON-NLS-1$
String[] paths = path1.split(";");
for (String path: paths) {
path = ELReference.getPath(pathAliases, path);
IPath pathObject = new Path(path);
addUnnamedResource(pathObject);
}
}
modifications = 0;
}
private boolean checkDeclaration(IPath resource, String variableName) {
Set<IPath> paths = resourcesByDeclaringVariableName.get(variableName);
if(paths!=null) {
for (IPath path : paths) {
if(path.equals(resource)) {
return true;
}
}
}
return false;
}
public int getModificationsSinceLastStore() {
return modifications;
}
public String getId() {
return id;
}
public boolean isEmpty() {
return (resourcesByVariableName == null || resourcesByVariableName.isEmpty()) && variableNamesByResource.isEmpty() && resourcesByDeclaringVariableName.isEmpty() && declaringVariableNamesByResource.isEmpty() && unnamedResources.isEmpty();
}
}