org.eclipse.xtext.serializer.analysis.GrammarConstraintProvider Maven / Gradle / Ivy
/*******************************************************************************
* Copyright (c) 2010 itemis AG (http://www.itemis.eu) 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.xtext.serializer.analysis;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.xtext.AbstractElement;
import org.eclipse.xtext.Action;
import org.eclipse.xtext.Alternatives;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CompoundElement;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.EnumRule;
import org.eclipse.xtext.Grammar;
import org.eclipse.xtext.GrammarUtil;
import org.eclipse.xtext.Group;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.UnorderedGroup;
import org.eclipse.xtext.grammaranalysis.impl.GrammarElementTitleSwitch;
import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterState;
import org.eclipse.xtext.serializer.analysis.ActionFilterNFAProvider.ActionFilterTransition;
import org.eclipse.xtext.util.EmfFormatter;
import org.eclipse.xtext.util.Pair;
import org.eclipse.xtext.util.Tuples;
import org.eclipse.xtext.util.formallang.ProductionFormatter;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;
import com.google.inject.Singleton;
/**
* @author Moritz Eysholdt - Initial contribution and API
*/
@Singleton
public class GrammarConstraintProvider implements IGrammarConstraintProvider {
protected abstract static class AbstractConstraintContext implements IConstraintContext {
protected List constraints = Lists.newArrayList();
protected String name;
private AbstractConstraintContext(String name) {
super();
this.name = name;
}
protected void addConstraint(Constraint constraint) {
constraints.add(constraint);
}
public List getConstraints() {
return constraints;
}
public String getName() {
return name;
}
protected void initConstraints() {
Collections.sort(constraints, new Comparator() {
public int compare(IConstraint o1, IConstraint o2) {
return o1.getName().compareTo(o2.getName());
}
});
for (IConstraint ele : constraints)
((Constraint) ele).initLists();
}
@Override
public String toString() {
Iterable constraintNames = Iterables.transform(constraints, new Function() {
public String apply(IConstraint from) {
return from.getName();
}
});
return getName() + ": " + Joiner.on(" | ").join(constraintNames) + ";";
}
}
protected static class ActionConstraint extends Constraint {
protected Action actionContext;
public ActionConstraint(Action context, EClass type, ConstraintElement body, GrammarConstraintProvider provider) {
super(type, body, provider);
this.actionContext = context;
}
@Override
protected EObject getMostSpecificContext() {
return body == null ? actionContext : body.getContext();
}
}
protected static class AssignedActionConstraintContext extends AbstractConstraintContext {
protected Action action;
public AssignedActionConstraintContext(Action action, String name) {
super(name);
this.action = action;
}
public EClass getCommonType() {
return (EClass) action.getType().getClassifier();
}
public EObject getContext() {
return action;
}
}
protected static abstract class Constraint implements IConstraint {
protected IConstraintElement[] assignments;
protected ConstraintElement body;
protected GrammarConstraintProvider provider;
protected IConstraintElement[] elements;
protected IFeatureInfo[] features;
protected String name;
protected EObject specificContext;
protected EClass type;
public Constraint(EClass type, ConstraintElement body, GrammarConstraintProvider provider) {
super();
this.type = type;
this.body = body;
if (this.body != null)
this.body.setContainingConstraint(this);
}
protected void collectElements(ConstraintElement ele, List elements,
List assignments, List[] assignmentsByFeature) {
ele.setElementId(elements.size());
elements.add(ele);
switch (ele.getType()) {
case ASSIGNED_ACTION_CALL:
case ASSIGNED_CROSSREF_DATATYPE_RULE_CALL:
case ASSIGNED_CROSSREF_ENUM_RULE_CALL:
case ASSIGNED_CROSSREF_TERMINAL_RULE_CALL:
case ASSIGNED_CROSSREF_KEYWORD:
case ASSIGNED_DATATYPE_RULE_CALL:
case ASSIGNED_ENUM_RULE_CALL:
case ASSIGNED_KEYWORD:
case ASSIGNED_PARSER_RULE_CALL:
case ASSIGNED_TERMINAL_RULE_CALL:
EClass type = ele.getContainingConstraint().getType();
EStructuralFeature feature = type.getEStructuralFeature(ele.getFeatureName());
if (feature == null)
throw new RuntimeException("Feature " + ele.getFeatureName() + " not found in "
+ type.getName());
int featureID = type.getFeatureID(feature);
List assignmentByFeature = assignmentsByFeature[featureID];
if (assignmentByFeature == null)
assignmentsByFeature[featureID] = assignmentByFeature = Lists.newArrayList();
ele.setFeatureAssignmentId(assignmentByFeature.size());
assignmentByFeature.add(ele);
ele.setAssignmentId(assignments.size());
assignments.add(ele);
return;
case ALTERNATIVE:
case GROUP:
for (IConstraintElement e : ele.getChildren())
collectElements((ConstraintElement) e, elements, assignments, assignmentsByFeature);
return;
}
}
public int compareTo(IConstraint o) {
return getName().compareTo(o.getName());
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof Constraint))
return false;
Constraint c = (Constraint) obj;
if (type != null && c.type != null) {
if (!type.getName().equals(c.type.getName()))
return false;
if (!type.getEPackage().getNsURI().equals(c.type.getEPackage().getNsURI()))
return false;
} else if (type != null || c.type != null)
return false;
return (body == null && c.body == null) || (body != null && body.equals(c.body));
}
public IConstraintElement[] getAssignments() {
return assignments;
}
public IConstraintElement getBody() {
return body;
}
protected Object getCacheKey() {
return Tuples.create(body.getContext(), type);
}
public IConstraintElement[] getElements() {
return elements;
}
public IFeatureInfo[] getFeatures() {
return features;
}
protected abstract EObject getMostSpecificContext();
public Iterable getMultiAssignementFeatures() {
List result = Lists.newArrayList();
for (IFeatureInfo info : features)
if (info != null && info.getAssignments().length > 1)
result.add(info);
return result;
}
public String getName() {
return name + "_" + (type == null ? "null" : type.getName());
}
public String getSimpleName() {
return name;
}
public Iterable getSingleAssignementFeatures() {
List result = Lists.newArrayList();
for (IFeatureInfo info : features)
if (info != null && info.getAssignments().length == 1)
result.add(info);
return result;
}
public EClass getType() {
return type;
}
@Override
public int hashCode() {
int result = body == null ? 0 : body.hashCode();
if (type != null) {
result += 7 * type.getName().hashCode();
result += 13 * type.getEPackage().getNsURI().hashCode();
}
return result;
}
protected void initLists() {
List ele = Lists.newArrayList();
List ass = Lists.newArrayList();
@SuppressWarnings("unchecked")
List[] feat = new List[getType() == null ? 0 : getType().getFeatureCount()];
if (body != null)
collectElements(body, ele, ass, feat);
elements = ele.toArray(new IConstraintElement[ele.size()]);
assignments = ass.toArray(new IConstraintElement[ass.size()]);
features = new IFeatureInfo[feat.length];
for (int i = 0; i < feat.length; i++)
if (feat[i] != null) {
EStructuralFeature feature = getType().getEStructuralFeature(i);
IConstraintElement[] fass = feat[i].toArray(new IConstraintElement[feat[i].size()]);
features[i] = new FeatureInfo(this, feature, fass);
}
}
protected void setName(String name) {
this.name = name;
}
@Override
public String toString() {
String typeName = getType() == null ? "null" : getType().getName();
String body = getBody() != null ? getBody().toString() : "{" + typeName + "}";
return getName() + " returns " + typeName + ": " + body + ";";
}
}
protected static class ConstraintElement implements IConstraintElement {
protected int assignmentId = -1;
// protected Boolean cardinalityOneForFeature;
protected List children;
protected List containedAssignments = null;
// protected IConstraintElement excludingAlternative = UNINTITIALIZED;
protected ConstraintElement container;
protected IConstraint containingConstraint;
protected EObject context;
protected List> dependingAssignments;
protected AbstractElement element;
protected URI elementURI;
protected int elementId = -1;
protected int featureAssignmentId = -1;
protected IFeatureInfo featureInfo;
protected boolean many;
protected boolean optional;
protected ConstraintElementType type;
protected boolean typeMatch = false;
protected ConstraintElement() {
}
protected ConstraintElement(EObject context, ConstraintElementType type) {
this(context, type, null, false, false);
}
protected ConstraintElement(EObject context, ConstraintElementType type, AbstractElement element) {
this(context, type, element, type != ConstraintElementType.ASSIGNED_ACTION_CALL
&& GrammarUtil.isMultipleCardinality(element), type != ConstraintElementType.ASSIGNED_ACTION_CALL
&& GrammarUtil.isOptionalCardinality(element));
}
protected ConstraintElement(EObject context, ConstraintElementType type, AbstractElement element, boolean many,
boolean optional) {
super();
this.context = context;
this.type = type;
this.element = element;
this.many = many;
this.optional = optional;
if (type == ConstraintElementType.ALTERNATIVE || type == ConstraintElementType.GROUP)
children = Lists.newArrayList();
}
protected void addAllChilden(ConstraintElement childrenOwner) {
for (IConstraintElement c : childrenOwner.children)
addChild((ConstraintElement) c);
childrenOwner.getChildren().clear();
if (childrenOwner.isTypeMatch())
typeMatch();
}
protected void addChild(ConstraintElement child) {
child.container = this;
if (child == INVALID || child == TYPEMATCH)
throw new RuntimeException("This is not a valid child: '" + child + "'");
this.children.add(child);
if (child.isTypeMatch())
typeMatch();
}
protected void collectDependingAssignmentsByContainer(IConstraintElement child,
List> result, boolean childMany,
boolean childOptional) {
IConstraintElement container = child.getContainer();
if (container == null)
return;
boolean cntOptional = container.isOptionalRecursive(null);
boolean cntMany = container.isManyRecursive(null);
switch (container.getType()) {
case ALTERNATIVE:
if (!container.isManyRecursive(null))
for (IConstraintElement choice : container.getChildren())
if (choice != child)
for (IConstraintElement ass : choice.getContainedAssignments())
result.add(Tuples.create(ass, RelationalDependencyType.EXCLUDE_IF_SET));
break;
case GROUP:
if (!cntOptional && !cntMany)
return;
for (IConstraintElement choice : container.getChildren())
if (choice != child)
for (IConstraintElement ass : choice.getContainedAssignments()) {
boolean assMany = ass.isManyRecursive(container);
boolean assOptional = ass.isOptionalRecursive(container);
boolean exclude_if_unset = !assOptional;
boolean mandatory_if_set = !childOptional;
boolean same = false, same_or_less = false, same_or_more = false;
if (cntMany) {
if (!assMany && !assOptional && !childMany && !childOptional)
same = true;
else if ((childOptional && !childMany && !assOptional)
|| (!assOptional && !childMany && !assOptional && assMany))
same_or_less = true;
else if ((!childOptional && !childMany && assOptional && !assMany)
|| (!childOptional && childMany && !assOptional && !assMany)
|| (!childOptional && childMany && assOptional && !assMany))
same_or_more = true;
}
if (exclude_if_unset && !same_or_less && !same)
result.add(Tuples.create(ass, RelationalDependencyType.EXCLUDE_IF_UNSET));
if (mandatory_if_set && !same_or_more && !same)
result.add(Tuples.create(ass, RelationalDependencyType.MANDATORY_IF_SET));
if (same)
result.add(Tuples.create(ass, RelationalDependencyType.SAME));
if (same_or_less)
result.add(Tuples.create(ass, RelationalDependencyType.SAME_OR_LESS));
if (same_or_more)
result.add(Tuples.create(ass, RelationalDependencyType.SAME_OR_MORE));
}
break;
default:
}
childMany = childMany || container.isMany();
childOptional = childOptional || container.isOptional()
|| container.getType() == ConstraintElementType.ALTERNATIVE;
collectDependingAssignmentsByContainer(container, result, childMany, childOptional);
}
protected boolean containsChild(IConstraintElement child) {
if (children == null)
return false;
for (IConstraintElement c : children)
if (c.equals(child))
return true;
return false;
}
protected String context2Name(EObject context) {
return ((Constraint) getContainingConstraint()).provider.context2Name.apply(context);
}
@Override
public boolean equals(Object obj) {
if (!(obj instanceof ConstraintElement))
return false;
ConstraintElement ce = (ConstraintElement) obj;
if (getElementURI().equals(ce.getElementURI())) {
if (children == null && ce.children == null)
return true;
if (children == null || ce.children == null)
return false;
else if (getType() == ConstraintElementType.ALTERNATIVE && children.size() == ce.children.size()) {
for (IConstraintElement child : children)
if (!ce.containsChild(child))
return false;
return true;
} else
return children.equals(ce.children);
}
return false;
}
protected IConstraintElement findCommonContainer(List elements) {
if (elements.size() == 0)
return null;
if (elements.size() == 1)
return elements.get(0);
IConstraintElement result = elements.get(0);
for (int i = 1; i < elements.size(); i++) {
boolean found = false;
while (!found && result != null) {
IConstraintElement cand = elements.get(i);
while (!found && cand != null)
if (cand == result)
found = true;
else
cand = cand.getContainer();
if (!found)
result = result.getContainer();
}
}
return result;
}
public Action getAction() {
return element instanceof Action ? (Action) element : null;
}
public int getAssignmentID() {
return this.assignmentId;
}
protected String getAssignmentOperator() {
if (element instanceof Action)
return ((Action) element).getOperator();
Assignment ass = GrammarUtil.containingAssignment(element);
if (ass != null)
return ass.getOperator();
return null;
}
public EObject getCallContext() {
switch (type) {
case ASSIGNED_ACTION_CALL:
return getAction();
case ASSIGNED_PARSER_RULE_CALL:
return getRuleCall().getRule();
default:
return null;
}
}
public String getCardinality() {
return isMany() ? (isOptional() ? "*" : "+") : (isOptional() ? "?" : "");
}
public List getChildren() {
return children;
}
public List getContainedAssignments() {
if (containedAssignments == null) {
containedAssignments = Lists.newArrayList();
if (assignmentId >= 0)
containedAssignments.add(this);
if (getChildren() != null)
for (IConstraintElement child : getChildren())
containedAssignments.addAll(child.getContainedAssignments());
}
return containedAssignments;
}
public IConstraintElement getContainer() {
return container;
}
public IConstraint getContainingConstraint() {
if (containingConstraint == null)
containingConstraint = getContainer().getContainingConstraint();
return containingConstraint;
}
protected EObject getContext() {
return context;
}
public CrossReference getCrossReference() {
if (element == null)
return null;
return GrammarUtil.containingCrossReference(element);
}
public EClass getCrossReferenceType() {
if (element == null)
return null;
CrossReference cr = GrammarUtil.containingCrossReference(element);
if (cr == null)
return null;
return (EClass) cr.getType().getClassifier();
}
public List> getDependingAssignment() {
if (assignmentId < 0)
return null;
if (dependingAssignments == null) {
dependingAssignments = Lists.newArrayList();
collectDependingAssignmentsByContainer(this, dependingAssignments, isMany(), isOptional());
}
return dependingAssignments;
}
public int getElementID() {
return elementId;
}
public EStructuralFeature getFeature() {
return getFeatureInfo().getFeature();
}
public int getFeatureAssignmentID() {
return featureAssignmentId;
}
public IFeatureInfo getFeatureInfo() {
return featureInfo;
}
protected String getFeatureName() {
if (element instanceof Action)
return ((Action) element).getFeature();
Assignment ass = GrammarUtil.containingAssignment(element);
if (ass != null)
return ass.getFeature();
return null;
}
// protected void insertChild(ConstraintElement child) {
// child.container = this;
// this.children.add(0, child);
// }
public AbstractElement getGrammarElement() {
return element;
}
public Keyword getKeyword() {
return element instanceof Keyword ? (Keyword) element : null;
}
public RuleCall getRuleCall() {
return element instanceof RuleCall ? (RuleCall) element : null;
}
public ConstraintElementType getType() {
return type;
}
protected URI getElementURI() {
if (elementURI == null)
elementURI = element == null ? URI.createURI("null") : EcoreUtil.getURI(element);
return elementURI;
}
@Override
public int hashCode() {
return getElementURI().hashCode();
// int result = element != null ? element.hashCode() : 0;
// // if (children != null)
// // result += 7 * children.hashCode();
// return result;
}
public boolean isCardinalityOneAmongAssignments(List assignments) {
if (assignments.size() < 2)
return false;
IConstraintElement commonContainer = findCommonContainer(assignments);
return commonContainer.getType() != ConstraintElementType.ALTERNATIVE
&& !isOptionalRecursive(commonContainer) && !isManyRecursive(commonContainer);
}
public boolean isMany() {
return many;
}
public boolean isManyRecursive(IConstraintElement root) {
return isMany() || (container != null && container != root && container.isManyRecursive(root));
}
public boolean isOptional() {
return optional && !typeMatch;
}
public boolean isOptionalRecursive(IConstraintElement root) {
if (isOptional())
return true;
if (getContainer() != root) {
if (getContainer().getType() == ConstraintElementType.ALTERNATIVE)
return true;
if (getContainer().isOptionalRecursive(root))
return true;
}
return false;
}
public boolean isRoot() {
return container == null;
}
protected boolean isTypeMatch() {
return this.typeMatch;
}
protected void setAssignmentId(int id) {
this.assignmentId = id;
}
protected void setContainingConstraint(IConstraint containingConstraint) {
this.containingConstraint = containingConstraint;
}
protected void setElementId(int id) {
this.elementId = id;
}
protected void setFeatureAssignmentId(int id) {
this.featureAssignmentId = id;
}
protected void setFeatureInfo(IFeatureInfo featureInfo) {
this.featureInfo = featureInfo;
}
//
// public IConstraintElement getExcludingAlternative() {
// if (excludingAlternative == UNINTITIALIZED) {
// IConstraintElement ele = getContainer();
// while (ele != null) {
// if (ele.getType() == ConstraintElementType.ALTERNATIVE) {
// if (ele.isManyRecursive(null))
// return excludingAlternative = null;
// else
// return excludingAlternative = ele;
//
// } else
// ele = ele.getContainer();
// }
// return excludingAlternative = null;
// }
// return excludingAlternative;
// }
protected void setMany(boolean many) {
this.many = many;
}
protected void setOptional(boolean optional) {
this.optional = optional;
}
@Override
public String toString() {
if (this == INVALID)
return "INVALID";
if (this == TYPEMATCH)
return "TYPEMATCH";
if (type == null)
return "error(type is null)";
GrammarElementTitleSwitch t2s = new GrammarElementTitleSwitch().hideCardinality().showActionsAsRuleCalls()
.showAssignments();
ProductionFormatter formatter = new ProductionFormatter();
formatter.setTokenToString(t2s);
return formatter.format(new ConstraintElementProduction(getContainingConstraint()), this, true);
}
protected void typeMatch() {
this.typeMatch = true;
}
}
protected static class FeatureInfo implements IFeatureInfo {
protected IConstraintElement[] assignments;
protected IConstraint constraint;
protected Boolean contentValidationNeeded;
protected List> dependingFeatures;
protected EStructuralFeature feature;
public FeatureInfo(IConstraint constraint, EStructuralFeature feature, IConstraintElement[] assignments) {
super();
this.constraint = constraint;
this.feature = feature;
this.assignments = assignments;
for (IConstraintElement ass : assignments)
((ConstraintElement) ass).setFeatureInfo(this);
}
public int getAssignmentCount() {
return assignments.length;
}
public IConstraintElement[] getAssignments() {
return assignments;
}
public IConstraint getContainingConstraint() {
return constraint;
}
public List> getDependingFeatures() {
if (dependingFeatures == null) {
dependingFeatures = Lists.newArrayList();
for (Pair p : getRelationalAssignemntConstraintIntersection())
dependingFeatures.add(Tuples.create(p.getFirst().getFeatureInfo(), p.getSecond()));
}
return dependingFeatures;
}
public EStructuralFeature getFeature() {
return feature;
}
public int getLowerBound() {
int result = 0;
for (IConstraintElement ass : getAssignments())
if (!ass.isOptionalRecursive(null))
result++;
return result; // TODO: consider assignments excluding each other
}
protected List> getRelationalAssignemntConstraintIntersection() {
List> r = getAssignments()[0].getDependingAssignment();
if (getAssignmentCount() == 1)
return r;
r = Lists.newArrayList(r);
for (int i = 1; i < getAssignments().length; i++)
for (int j = r.size() - 1; j >= 0; j--)
if (getAssignments()[i] == r.get(j).getFirst()
|| !getAssignments()[i].getDependingAssignment().contains(r.get(j)))
r.remove(j);
return r;
}
public int getUpperBound() {
for (IConstraintElement ass : getAssignments())
if (ass.isManyRecursive(null))
return -1;
return getAssignmentCount(); // TODO: consider assignments excluding each other
}
public boolean isContentValidationNeeded() {
if (contentValidationNeeded != null)
return contentValidationNeeded;
contentValidationNeeded = false;
if (assignments.length >= 2) {
IConstraintElement first = assignments[0];
if (first.getType() == ConstraintElementType.ASSIGNED_ACTION_CALL)
contentValidationNeeded = true;
else
for (int i = 1; i < assignments.length; i++) {
IConstraintElement a = assignments[i];
if (a.getType() == ConstraintElementType.ASSIGNED_ACTION_CALL
|| first.getCrossReferenceType() != a.getCrossReferenceType()
|| !EcoreUtil.equals(first.getGrammarElement(), a.getGrammarElement())) {
contentValidationNeeded = true;
break;
}
}
}
return contentValidationNeeded;
}
@Override
public String toString() {
StringBuilder b = new StringBuilder();
b.append(feature.getName());
b.append("[");
b.append(getLowerBound());
b.append(", ");
b.append(getUpperBound() == -1 ? "*" : getUpperBound());
b.append("]");
for (Pair rel : getDependingFeatures()) {
b.append("\n ");
b.append(rel.getSecond());
b.append(" ");
b.append(rel.getFirst().getFeature().getName());
}
return b.toString();
}
public List getCalledContexts() {
List result = Lists.newArrayList();
for (IConstraintElement ass : getAssignments()) {
EObject ctx = ass.getCallContext();
if (ctx != null)
result.add(ctx);
}
return result;
}
}
protected static class ParserRuleConstraintContext extends AbstractConstraintContext {
protected ParserRule rule;
public ParserRuleConstraintContext(ParserRule rule, String name) {
super(name);
this.rule = rule;
}
public EClass getCommonType() {
return (EClass) rule.getType().getClassifier();
}
public EObject getContext() {
return rule;
}
}
protected static class RuleConstraint extends Constraint {
protected ParserRule context;
public RuleConstraint(ParserRule context, EClass type, ConstraintElement body,
GrammarConstraintProvider provider) {
super(type, body, provider);
this.context = context;
}
@Override
protected EObject getMostSpecificContext() {
return body == null ? context : body.getContext();
}
}
protected final static ConstraintElement INVALID = new ConstraintElement();
protected final static ConstraintElement TYPEMATCH = new ConstraintElement() {
@Override
protected boolean isTypeMatch() {
return true;
}
};
protected final static ConstraintElement UNINTITIALIZED = new ConstraintElement();
protected Map> cache = Maps.newHashMap();
@Inject
protected Context2NameFunction context2Name;
@Inject
protected IContextProvider contextProvider;
@Inject
protected ActionFilterNFAProvider nfaProvider;
protected ConstraintElement createConstraintElement(EObject context, AbstractElement ele, EClass requiredType,
Set
© 2015 - 2025 Weber Informatics LLC | Privacy Policy