|
|
|
@@ -0,0 +1,436 @@
|
|
|
|
|
package flintstones.method.dEvaluation.phase.aggregation;
|
|
|
|
|
|
|
|
|
|
import flintstones.domain.fuzzyset.function.types.TrapezoidalFunction;
|
|
|
|
|
import flintstones.entity.method.phase.PhaseMethod;
|
|
|
|
|
import flintstones.entity.operator.AggregationOperator;
|
|
|
|
|
import flintstones.entity.problemelement.ProblemElementKey;
|
|
|
|
|
import flintstones.entity.problemelement.entities.Alternative;
|
|
|
|
|
import flintstones.entity.problemelement.entities.Criterion;
|
|
|
|
|
import flintstones.entity.problemelement.entities.Expert;
|
|
|
|
|
import flintstones.entity.problemelement.entities.ProblemElement;
|
|
|
|
|
import flintstones.entity.valuation.Valuation;
|
|
|
|
|
import flintstones.helper.data.HashMatrix;
|
|
|
|
|
import flintstones.helper.data.Pair;
|
|
|
|
|
import flintstones.model.problemelement.service.IProblemElementService;
|
|
|
|
|
import flintstones.operator.aggregation.arithmeticmean.ArithmeticMean;
|
|
|
|
|
import flintstones.operator.service.IOperatorService;
|
|
|
|
|
import flintstones.valuation.fuzzy.FuzzyValuation;
|
|
|
|
|
|
|
|
|
|
import java.io.File;
|
|
|
|
|
import java.io.FileNotFoundException;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.HashMap;
|
|
|
|
|
import java.util.LinkedHashMap;
|
|
|
|
|
import java.util.Scanner;
|
|
|
|
|
|
|
|
|
|
import javax.inject.Inject;
|
|
|
|
|
|
|
|
|
|
public class DEvaluationAggregationModel extends PhaseMethod {
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
private IProblemElementService problemService;
|
|
|
|
|
|
|
|
|
|
@Inject
|
|
|
|
|
private IOperatorService operatorService;
|
|
|
|
|
|
|
|
|
|
private HashMap<ProblemElementKey, Valuation> fuzzyValuations;
|
|
|
|
|
|
|
|
|
|
private LinkedHashMap<ProblemElement, Double> expertsWeights;
|
|
|
|
|
private HashMap<Pair<ProblemElement, ProblemElement>, Double> criteriaWeightsByExpert;
|
|
|
|
|
private HashMap<Pair<ProblemElement, ProblemElement>, AggregationOperator> criteriaAggregationOperatorsByExpert;
|
|
|
|
|
private HashMap<ProblemElement, FuzzyValuation[][]> decisionMatricesByExpert;
|
|
|
|
|
private FuzzyValuation[][] decisionMatrix;
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public String getName() {
|
|
|
|
|
return "Aggregation";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void loadData() {
|
|
|
|
|
try {
|
|
|
|
|
File data = new File(
|
|
|
|
|
"C:\\Users\\Álvaro\\Workspaces\\flintstones_2020\\flintstones\\git\\bundles\\flintstones.method.dEvaluation\\data\\data.txt");
|
|
|
|
|
Scanner myReader = new Scanner(data);
|
|
|
|
|
while (myReader.hasNextLine()) {
|
|
|
|
|
String line = myReader.nextLine();
|
|
|
|
|
String[] split = line.split(";");
|
|
|
|
|
if(split.length == 3) {
|
|
|
|
|
String expertS = split[0];
|
|
|
|
|
String criterionS = split[1];
|
|
|
|
|
String weightS = split[2];
|
|
|
|
|
|
|
|
|
|
ProblemElement expert = getExpert(expertS);
|
|
|
|
|
ProblemElement criterion = getCriterion(criterionS);
|
|
|
|
|
Double weight = Double.parseDouble(weightS);
|
|
|
|
|
|
|
|
|
|
criteriaWeightsByExpert.put(new Pair<ProblemElement, ProblemElement>(expert, criterion), weight);
|
|
|
|
|
} else {
|
|
|
|
|
String expertS = split[0];
|
|
|
|
|
String weightS = split[1];
|
|
|
|
|
|
|
|
|
|
ProblemElement expert = getExpert(expertS);
|
|
|
|
|
Double weight = Double.parseDouble(weightS);
|
|
|
|
|
|
|
|
|
|
expertsWeights.put(expert, weight);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
myReader.close();
|
|
|
|
|
} catch (FileNotFoundException e) {
|
|
|
|
|
System.out.println("An error occurred.");
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@SuppressWarnings("unchecked")
|
|
|
|
|
public void init() {
|
|
|
|
|
expertsWeights = new LinkedHashMap<>();
|
|
|
|
|
criteriaWeightsByExpert = new HashMap<>();
|
|
|
|
|
criteriaAggregationOperatorsByExpert = new HashMap<>();
|
|
|
|
|
decisionMatricesByExpert = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
fuzzyValuations = (HashMap<ProblemElementKey, Valuation>) this.importData("twoTupleUnifiedValuations");
|
|
|
|
|
|
|
|
|
|
initAggregationOperators();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void initAggregationOperators() {
|
|
|
|
|
AggregationOperator[] operators = operatorService.getAggregationOperatorsFor(FuzzyValuation.ID, true);
|
|
|
|
|
|
|
|
|
|
for (ProblemElement expert : getExperts()) {
|
|
|
|
|
for (ProblemElement criterion : getParentCriteria())
|
|
|
|
|
criteriaAggregationOperatorsByExpert.put(new Pair<ProblemElement, ProblemElement>(expert, criterion),
|
|
|
|
|
operators[0]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void addExpertWeight(String expert, Double weight) {
|
|
|
|
|
expertsWeights.put(getExpert(expert), weight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void removeExpertWeight(String expert) {
|
|
|
|
|
expertsWeights.remove(getExpert(expert));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void addCriterionWeight(String expert, String criterion, Double weight) {
|
|
|
|
|
criteriaWeightsByExpert
|
|
|
|
|
.put(new Pair<ProblemElement, ProblemElement>(getExpert(expert), getCriterion(criterion)), weight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void removeCriterionWeight(String expert, String criterion) {
|
|
|
|
|
criteriaWeightsByExpert
|
|
|
|
|
.remove(new Pair<ProblemElement, ProblemElement>(getExpert(expert), getCriterion(criterion)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public Double getCriterionWeight(String expert, String criterion) {
|
|
|
|
|
return criteriaWeightsByExpert
|
|
|
|
|
.get(new Pair<ProblemElement, ProblemElement>(getExpert(expert), getCriterion(criterion)));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AggregationOperator getAggregationOperator(String expert, String criterion) {
|
|
|
|
|
Pair<ProblemElement, ProblemElement> key = new Pair<ProblemElement, ProblemElement>(getExpert(expert),
|
|
|
|
|
getCriterion(criterion));
|
|
|
|
|
return criteriaAggregationOperatorsByExpert.get(key);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AggregationOperator setAggregationOperator(String operatorName, String expert, String criterion) {
|
|
|
|
|
Pair<ProblemElement, ProblemElement> key = new Pair<ProblemElement, ProblemElement>(getExpert(expert),
|
|
|
|
|
getCriterion(criterion));
|
|
|
|
|
|
|
|
|
|
AggregationOperator operator = criteriaAggregationOperatorsByExpert.get(key);
|
|
|
|
|
|
|
|
|
|
if (!operator.getName().equals(operatorName)) {
|
|
|
|
|
operator = getOperator(operatorName);
|
|
|
|
|
criteriaAggregationOperatorsByExpert.put(key, operator);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return operator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public HashMap<String, Double> getCriteriaParentWeights(String expert) {
|
|
|
|
|
String[] criteria = getCriteriaParentName();
|
|
|
|
|
|
|
|
|
|
HashMap<String, Double> result = new HashMap<>();
|
|
|
|
|
for (String criterion : criteria)
|
|
|
|
|
result.put(criterion, getCriterionWeight(expert, criterion));
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public HashMap<String, Double> getSubcriteriaWeights(String expert, String criterion) {
|
|
|
|
|
String[] subcriteria = getCriterionChildrenName(criterion);
|
|
|
|
|
|
|
|
|
|
HashMap<String, Double> result = new HashMap<>();
|
|
|
|
|
for (String subcriterion : subcriteria)
|
|
|
|
|
result.put(subcriterion, getCriterionWeight(expert, subcriterion));
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FuzzyValuation[][] computeDecisionMatrixByExpert(ProblemElement expert) {
|
|
|
|
|
ProblemElement[] alternatives = getAlternatives();
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
|
|
|
|
|
ProblemElement alternative, criterion;
|
|
|
|
|
AggregationOperator operator;
|
|
|
|
|
ArrayList<Valuation> valuationsToAggregate;
|
|
|
|
|
ArrayList<Double> weights;
|
|
|
|
|
|
|
|
|
|
FuzzyValuation[][] decisionMatrix = new FuzzyValuation[alternatives.length][criteria.length];
|
|
|
|
|
FuzzyValuation aggregatedValuation;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < alternatives.length; ++i) {
|
|
|
|
|
alternative = alternatives[i];
|
|
|
|
|
for (int j = 0; j < criteria.length; ++j) {
|
|
|
|
|
criterion = criteria[j];
|
|
|
|
|
valuationsToAggregate = new ArrayList<Valuation>();
|
|
|
|
|
weights = new ArrayList<Double>();
|
|
|
|
|
for (ProblemElement subcriterion : criterion.getChildren()) {
|
|
|
|
|
valuationsToAggregate
|
|
|
|
|
.add(fuzzyValuations.get(new ProblemElementKey(expert, subcriterion, alternative)));
|
|
|
|
|
weights.add(getCriterionWeight(expert.getName(), subcriterion.getName()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
operator = criteriaAggregationOperatorsByExpert
|
|
|
|
|
.get(new Pair<ProblemElement, ProblemElement>(expert, criterion));
|
|
|
|
|
|
|
|
|
|
aggregatedValuation = (FuzzyValuation) operator.computeAggregation(valuationsToAggregate, weights);
|
|
|
|
|
aggregatedValuation.setFuzzyNumber(aggregatedValuation.getFuzzyNumber().multiplicationScalar(
|
|
|
|
|
criteriaWeightsByExpert.get(new Pair<ProblemElement, ProblemElement>(expert, criterion))));
|
|
|
|
|
|
|
|
|
|
decisionMatrix[i][j] = aggregatedValuation;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decisionMatricesByExpert.put(expert, decisionMatrix);
|
|
|
|
|
|
|
|
|
|
return decisionMatrix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public FuzzyValuation[][] computeDecisionMatrix() {
|
|
|
|
|
ProblemElement[] alternatives = getAlternatives();
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
|
|
|
|
|
decisionMatrix = new FuzzyValuation[alternatives.length][criteria.length];
|
|
|
|
|
|
|
|
|
|
AggregationOperator arithmeticMean = operatorService.getAggregationOperator(ArithmeticMean.ID,
|
|
|
|
|
FuzzyValuation.ID);
|
|
|
|
|
ArrayList<Valuation> valuations;
|
|
|
|
|
ArrayList<Double> weights;
|
|
|
|
|
|
|
|
|
|
FuzzyValuation[][] decisionMatrixExpert;
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < getAlternatives().length; ++i) {
|
|
|
|
|
for (int j = 0; j < getCriteriaParentName().length; ++j) {
|
|
|
|
|
valuations = new ArrayList<Valuation>();
|
|
|
|
|
weights = new ArrayList<Double>();
|
|
|
|
|
for (ProblemElement expert : getExperts()) {
|
|
|
|
|
decisionMatrixExpert = decisionMatricesByExpert.get(expert);
|
|
|
|
|
valuations.add(decisionMatrixExpert[i][j]);
|
|
|
|
|
weights.add(expertsWeights.get(expert));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
decisionMatrix[i][j] = (FuzzyValuation) arithmeticMean.computeAggregation(valuations, weights);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
exportDecisionMatrix();
|
|
|
|
|
|
|
|
|
|
return decisionMatrix;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private void exportDecisionMatrix() {
|
|
|
|
|
|
|
|
|
|
//Export decision matrix for TOPSIS
|
|
|
|
|
HashMatrix<ProblemElement, ProblemElement, TrapezoidalFunction> decisionMatrix = transformDecisionMatrixToFuzzyTOPSIS();
|
|
|
|
|
ArrayList<TrapezoidalFunction> weights = transformWeightsToFuzzyTOPSIS();
|
|
|
|
|
|
|
|
|
|
this.exportData("aggregatedValues", decisionMatrix, true);
|
|
|
|
|
this.exportData("aggregatedWeights", weights, true);
|
|
|
|
|
|
|
|
|
|
//Export decision matrix for sensitive analysis
|
|
|
|
|
HashMatrix<ProblemElement, ProblemElement, Valuation> decisionMatrixSA = transformDecisionMatrixToSensitiveAnalysis();
|
|
|
|
|
HashMap<ProblemElement, Double> weightsSA = transformWeightsToSensitiveAnalysis();
|
|
|
|
|
|
|
|
|
|
this.exportData("decisionmatrix", decisionMatrixSA, true);
|
|
|
|
|
this.exportData("criterionWeights", weightsSA, true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HashMatrix<ProblemElement, ProblemElement, TrapezoidalFunction> transformDecisionMatrixToFuzzyTOPSIS() {
|
|
|
|
|
HashMatrix<ProblemElement, ProblemElement, TrapezoidalFunction> dm = new HashMatrix<>();
|
|
|
|
|
|
|
|
|
|
ProblemElement[] alternatives = getAlternatives();
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < alternatives.length; ++i) {
|
|
|
|
|
for(int j = 0; j < criteria.length; ++j) {
|
|
|
|
|
dm.put(alternatives[i], criteria[j], decisionMatrix[i][j].getFuzzyNumber());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private ArrayList<TrapezoidalFunction> transformWeightsToFuzzyTOPSIS() {
|
|
|
|
|
ArrayList<TrapezoidalFunction> weights = new ArrayList<>();
|
|
|
|
|
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
ProblemElement[] experts = getExperts();
|
|
|
|
|
|
|
|
|
|
double aux, weight;
|
|
|
|
|
for(ProblemElement criterion: criteria) {
|
|
|
|
|
weight = 0;
|
|
|
|
|
for(ProblemElement expert: experts) {
|
|
|
|
|
aux = criteriaWeightsByExpert.get(new Pair<ProblemElement, ProblemElement>(expert, criterion));
|
|
|
|
|
aux *= expertsWeights.get(expert);
|
|
|
|
|
weight += aux;
|
|
|
|
|
}
|
|
|
|
|
weights.add(new TrapezoidalFunction(weight, weight, weight, weight));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return weights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HashMap<ProblemElement, Double> transformWeightsToSensitiveAnalysis() {
|
|
|
|
|
HashMap<ProblemElement, Double> weights = new HashMap<>();
|
|
|
|
|
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
ProblemElement[] experts = getExperts();
|
|
|
|
|
|
|
|
|
|
double aux, weight;
|
|
|
|
|
for(ProblemElement criterion: criteria) {
|
|
|
|
|
weight = 0;
|
|
|
|
|
for(ProblemElement expert: experts) {
|
|
|
|
|
aux = criteriaWeightsByExpert.get(new Pair<ProblemElement, ProblemElement>(expert, criterion));
|
|
|
|
|
aux *= expertsWeights.get(expert);
|
|
|
|
|
weight += aux;
|
|
|
|
|
}
|
|
|
|
|
weights.put(criterion, weight);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return weights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private HashMatrix<ProblemElement, ProblemElement, Valuation> transformDecisionMatrixToSensitiveAnalysis() {
|
|
|
|
|
HashMatrix<ProblemElement, ProblemElement, Valuation> dm = new HashMatrix<>();
|
|
|
|
|
|
|
|
|
|
ProblemElement[] alternatives = getAlternatives();
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
|
|
|
|
|
for(int i = 0; i < alternatives.length; ++i) {
|
|
|
|
|
for(int j = 0; j < criteria.length; ++j) {
|
|
|
|
|
dm.put(alternatives[i], criteria[j], decisionMatrix[i][j]);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return dm;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement[] getExperts() {
|
|
|
|
|
return problemService.getAll(Expert.Type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement getExpert(String name) {
|
|
|
|
|
return problemService.getByName(Expert.Type, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement[] getAlternatives() {
|
|
|
|
|
return problemService.getAll(Alternative.Type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement[] getParentCriteria() {
|
|
|
|
|
return problemService.getMainElements(Criterion.Type);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement[] getCriterionChildren(String name) {
|
|
|
|
|
ProblemElement criterion = problemService.getByName(Criterion.Type, name);
|
|
|
|
|
return criterion.getChildren();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public ProblemElement getCriterion(String name) {
|
|
|
|
|
return problemService.getByName(Criterion.Type, name);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AggregationOperator[] getOperators() {
|
|
|
|
|
AggregationOperator[] operators = operatorService.getAggregationOperatorsFor(FuzzyValuation.ID, true);
|
|
|
|
|
return operators;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public AggregationOperator getOperator(String operatorName) {
|
|
|
|
|
AggregationOperator[] operators = getOperators();
|
|
|
|
|
|
|
|
|
|
for (AggregationOperator operator : operators) {
|
|
|
|
|
if (operator.getName().equals(operatorName))
|
|
|
|
|
return operator;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public LinkedHashMap<ProblemElement, Double> getExpertsWeights() {
|
|
|
|
|
return expertsWeights;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getExpertsName() {
|
|
|
|
|
ProblemElement[] experts = getExperts();
|
|
|
|
|
|
|
|
|
|
String[] names = new String[experts.length];
|
|
|
|
|
for (int i = 0; i < names.length; ++i)
|
|
|
|
|
names[i] = experts[i].getName();
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getCriteriaParentName() {
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
|
|
|
|
|
String[] names = new String[criteria.length];
|
|
|
|
|
for (int i = 0; i < names.length; ++i)
|
|
|
|
|
names[i] = criteria[i].getName();
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getCriterionChildrenName(String name) {
|
|
|
|
|
ProblemElement[] children = getCriterionChildren(name);
|
|
|
|
|
|
|
|
|
|
String[] names = new String[children.length];
|
|
|
|
|
for (int i = 0; i < names.length; ++i)
|
|
|
|
|
names[i] = children[i].getName();
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getShortenedCriteriaNames() {
|
|
|
|
|
ProblemElement[] criteria = getParentCriteria();
|
|
|
|
|
String[] str = new String[criteria.length];
|
|
|
|
|
|
|
|
|
|
for (int i = 0; i < criteria.length; i++)
|
|
|
|
|
str[i] = "C" + (i + 1);
|
|
|
|
|
|
|
|
|
|
return str;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getAlternativesNames() {
|
|
|
|
|
ProblemElement[] alternatives = getAlternatives();
|
|
|
|
|
|
|
|
|
|
String[] names = new String[alternatives.length];
|
|
|
|
|
for (int i = 0; i < names.length; ++i)
|
|
|
|
|
names[i] = alternatives[i].getName();
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public String[] getOperatorsName() {
|
|
|
|
|
AggregationOperator[] operators = getOperators();
|
|
|
|
|
|
|
|
|
|
String[] names = new String[operators.length];
|
|
|
|
|
for (int i = 0; i < names.length; ++i)
|
|
|
|
|
names[i] = operators[i].getName();
|
|
|
|
|
|
|
|
|
|
return names;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public boolean isFinished() {
|
|
|
|
|
return decisionMatrix != null;
|
|
|
|
|
}
|
|
|
|
|
}
|