diff --git a/default.weights b/default.weights new file mode 100644 index 000000000..794e36531 --- /dev/null +++ b/default.weights @@ -0,0 +1,37 @@ +#Fri Feb 03 11:20:12 CET 2017 +privacy.sensitive-data=1.0 +mitigating.privacy-policy=1.0 +privacy.injury-serious=1.0 +motives.recipient-only-choice=1.0 +motives=1.0 +motives.recipient-wants-harm=1.0 +mitigating.security-documentation=1.0 +mitigating.staff-confidential=1.0 +privacy.injury-risk=1.0 +motives.re-identify-resources=1.0 +mitigating.staff-trained=1.0 +mitigating.sharing-forbids=1.0 +privacy.sensitive-context=1.0 +privacy.foreign-laws-risk=1.0 +mitigating.physically-privatearea=1.0 +mitigating.sharing-dblimits=1.0 +privacy.large-database=1.0 +mitigating.staff-restricted=1.0 +mitigating.breach-protocol=1.0 +mitigating.data-destruction=1.0 +mitigating.physically-secure=1.0 +motives.criminal-value=1.0 +motives.re-identify-expertise=1.0 +mitigating.system-full-control=1.0 +mitigating.sharing-enforceable=1.0 +mitigating.privacy-on-site=1.0 +privacy.highly-detailed=1.0 +mitigating.recipient-assessment=1.0 +mitigating=1.0 +privacy.no-promise=1.0 +motives.re-identify-motive=1.0 +privacy.no-expectation=1.0 +mitigating.sharing-audits=1.0 +privacy.no-guarantee=1.0 +mitigating.system-backlog=1.0 +privacy=1.0 \ No newline at end of file diff --git a/src/gui/org/deidentifier/arx/gui/Controller.java b/src/gui/org/deidentifier/arx/gui/Controller.java index 2def92962..ad68ef586 100644 --- a/src/gui/org/deidentifier/arx/gui/Controller.java +++ b/src/gui/org/deidentifier/arx/gui/Controller.java @@ -1474,6 +1474,13 @@ public void actionMenuFileSaveAs() { public void actionMenuHelpAbout() { main.showAboutDialog(); } + + /** + * Shows the "risk analysis" dialog. + */ + public void actionMenuHelpChecklistWizard() { + main.showChecklistWizard(); + } /** * Shows the "debug" dialog. diff --git a/src/gui/org/deidentifier/arx/gui/model/Model.java b/src/gui/org/deidentifier/arx/gui/model/Model.java index 382ddfda5..db57517d1 100644 --- a/src/gui/org/deidentifier/arx/gui/model/Model.java +++ b/src/gui/org/deidentifier/arx/gui/model/Model.java @@ -49,6 +49,9 @@ import org.deidentifier.arx.io.CSVSyntax; import org.deidentifier.arx.metric.MetricConfiguration; import org.deidentifier.arx.metric.MetricDescription; +import org.deidentifier.arx.risk.HIPAAConstants; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.deidentifier.arx.risk.RiskQuestionnaireWeights; /** * This class implements a large portion of the model used by the GUI. @@ -304,6 +307,14 @@ public static enum Perspective { /** Model */ private ModelClassification classificationModel = new ModelClassification(); + /* ***************************************** + * RISK WIZARD + ******************************************/ + /** Current configuration for the risk wizard */ + private RiskQuestionnaireWeights riskQuestionnaireWeights = null; + /** Current configuration for the risk wizard */ + private RiskQuestionnaire riskQuestionnaire = null; + /* ***************************************** * Information about the last anonymization process * ***************************************** @@ -1044,6 +1055,29 @@ public ModelRisk getRiskModel() { return riskModel; } + /** + * Returns the risk wizard configuration + * @return + * @throws IOException + */ + public RiskQuestionnaire getRiskQuestionnaire() throws IOException { + if (this.riskQuestionnaire == null) { + this.riskQuestionnaire = new RiskQuestionnaire(HIPAAConstants.getUSData()); + } + return this.riskQuestionnaire; + } + + /** + * Returns the risk wizard configuration + * @return + */ + public RiskQuestionnaireWeights getRiskQuestionnaireWeights() { + if (this.riskQuestionnaireWeights == null) { + this.riskQuestionnaireWeights = new RiskQuestionnaireWeights(); + } + return this.riskQuestionnaireWeights; + } + /** * Returns the currently selected attribute. * @@ -1108,7 +1142,7 @@ public String[] getSelectedFeaturesAsArray() { public ARXNode getSelectedNode() { return selectedNode; } - + /** * Returns a set of quasi identifiers selected for risk analysis * @return @@ -1697,6 +1731,15 @@ public void setResult(final ARXResult result) { setModified(); } + /** + * Sets the risk wizard configuration + * @param weights + * @return + */ + public void setRiskQuestionnaireWeights(RiskQuestionnaireWeights weights) { + this.riskQuestionnaireWeights = weights; + } + /** * Sets the selected attribute. * @@ -1765,9 +1808,9 @@ public void setSelectedQuasiIdentifiers(Set set) { this.selectedQuasiIdentifiers = set; this.setModified(); } - - /** - * + + /** + * Delegate method * * @param snapshotSize */ diff --git a/src/gui/org/deidentifier/arx/gui/resources/messages.properties b/src/gui/org/deidentifier/arx/gui/resources/messages.properties index 6c2542730..8edea9ba1 100644 --- a/src/gui/org/deidentifier/arx/gui/resources/messages.properties +++ b/src/gui/org/deidentifier/arx/gui/resources/messages.properties @@ -295,6 +295,7 @@ MainMenu.21=Anonymize MainMenu.23=Create hierarchy... MainMenu.25=Settings... MainMenu.27=Help +MainMenu.28=Risk analysis questionnaire... MainMenu.29=About MainMenu.3=New project... MainMenu.30=Find/replace... @@ -377,6 +378,8 @@ MainWindow.5=Error MainWindow.6=This dialog can only be used for data types with format MainWindow.7=Default MainWindow.8=Error\! +MainWindow.9=An unexpected error happened ( +MainWindow.30=Error opening questionnaire MainWindow.9=An unexpected error occurred ( Model.0a=Threshold must be in range [0,1] Model.0b=Internal error: invalid variant of risk-based privacy model @@ -1417,6 +1420,10 @@ ViewStatisticsClassificationInput.11=Sensitivity ViewStatisticsClassificationInput.12=Baseline ROC ViewStatisticsClassificationInput.13=Relative accuracy ViewStatisticsClassificationInput.14=Baseline AUC +ViewStatisticsClassificationInput.15=Precision +ViewStatisticsClassificationInput.16=Recall +ViewStatisticsClassificationInput.17=Value [%] +ViewStatisticsClassificationInput.18=F-Score ViewStatisticsClassificationInput.19=True positive rate ViewStatisticsClassificationInput.20=False positive rate ViewStatisticsClassificationInput.21=Target variable: @@ -1429,6 +1436,27 @@ ViewStatisticsClassificationInput.27=Overview ViewStatisticsClassificationInput.28=ROC curves ViewStatisticsClassificationInput.29=Update ViewStatisticsClassificationInput.30=Brier skill score +RiskWizard.0=Risk analysis questionnaire +RiskWizard.1=Answer the following questions +RiskWizard.2=Total +RiskWizard.3=Yes +RiskWizard.4=No +RiskWizard.5=N/A +RiskWizard.6=Edit +RiskWizard.7=Load +RiskWizard.8=Save +RiskWizard.9=Evaluation +RiskWizard.10=Risk Evaluation +RiskWizard.11=This is the risk evaluation based on your answers. +RiskWizard.12=Monitor +RiskWizard.13=Stacks +RiskWizard.15=Positive +RiskWizard.16=Neutral +RiskWizard.17=Negative +RiskWizard.18=Weighted Answers +RiskWizard.19=Visualization: +RiskWizard.20=Error saving weights +RiskWizard.21=Error loading weights ViewStatisticsQuality.0=Attribute ViewStatisticsQuality.1=Data type ViewStatisticsQuality.10=Dataset-level quality diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/MainWindow.java b/src/gui/org/deidentifier/arx/gui/view/impl/MainWindow.java index 4dae09598..243623b5c 100644 --- a/src/gui/org/deidentifier/arx/gui/view/impl/MainWindow.java +++ b/src/gui/org/deidentifier/arx/gui/view/impl/MainWindow.java @@ -73,6 +73,8 @@ import org.deidentifier.arx.gui.view.impl.menu.DialogTopBottomCoding; import org.deidentifier.arx.gui.view.impl.risk.LayoutRisks; import org.deidentifier.arx.gui.view.impl.utility.LayoutUtility; +import org.deidentifier.arx.gui.view.impl.wizard.RiskWizard; +import org.deidentifier.arx.gui.view.impl.wizard.RiskWizardDialog; import org.deidentifier.arx.gui.worker.Worker; import org.eclipse.jface.dialogs.IInputValidator; import org.eclipse.jface.dialogs.InputDialog; @@ -528,6 +530,18 @@ public void showHelpDialog(String id) { this.showErrorDialog(Resources.getMessage("MainWindow.12"), e); //$NON-NLS-1$ } } + + /** + * Shows the checklist wizard + */ + public void showChecklistWizard() { + try { + RiskWizardDialog dialog = new RiskWizardDialog(new RiskWizard(controller)); + dialog.open(); + } catch (Exception e) { + controller.actionShowInfoDialog(this.getShell(), Resources.getMessage("Controller.13"), Resources.getMessage("MainWindow.30")); //$NON-NLS-1$ //$NON-NLS-2$ + } + } /** * Shows an info dialog. @@ -1077,6 +1091,15 @@ private MainMenuItem getMenuHelp() { items.add(new MainMenuSeparator()); + items.add(new MainMenuItem(Resources.getMessage("MainMenu.28"), //$NON-NLS-1$ + controller.getResources().getManagedImage("information.png"), //$NON-NLS-1$ + true) { + public void action(Controller controller) { controller.actionMenuHelpChecklistWizard(); } + public boolean isEnabled(Model model) { return true; } + }); + + items.add(new MainMenuSeparator()); + items.add(new MainMenuItem(Resources.getMessage("MainMenu.29"), //$NON-NLS-1$ controller.getResources().getManagedImage("information.png"), //$NON-NLS-1$ false) { diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/HierarchyWizard.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/HierarchyWizard.java index 904eb3165..34dcc6275 100644 --- a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/HierarchyWizard.java +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/HierarchyWizard.java @@ -372,4 +372,4 @@ protected Button getLoadButton(){ protected Button getSaveButton(){ return super.getButton(buttonSave); } -} \ No newline at end of file +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizard.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizard.java new file mode 100644 index 000000000..b34f1f6cd --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizard.java @@ -0,0 +1,113 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import java.io.IOException; + +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.deidentifier.arx.risk.RiskQuestionnaireSection; +import org.eclipse.jface.wizard.Wizard; + +/** + * The questionnaire wizard for evaluating data sharing risks + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizard extends Wizard { + + /** Array containing each section's wizard page */ + protected RiskWizardPageSection[] pages; + + /** Final page showing the evaluation */ + protected RiskWizardPageEvaluation evaluationPage; + + /** The questionnaire used for the wizard */ + private RiskQuestionnaire questionnaire; + + /** Controller */ + private Controller controller; + + /** + * Create a new questionnaire wizard + * + * @param controller + * @throws IOException + */ + public RiskWizard(Controller controller) throws IOException { + super(); + this.questionnaire = controller.getModel().getRiskQuestionnaire(); + this.questionnaire.setWeights(controller.getModel().getRiskQuestionnaireWeights()); + this.controller = controller; + this.setWindowTitle(Resources.getMessage("RiskWizard.0")); + } + + @Override + public void addPages() { + // add a page for each section + RiskQuestionnaireSection[] sections = questionnaire.getSections(); + pages = new RiskWizardPageSection[sections.length]; + for (int i = 0; i < sections.length; i++) { + RiskQuestionnaireSection s = sections[i]; + RiskWizardPageSection p = new RiskWizardPageSection(s); + this.addPage(p); + pages[i] = p; + } + + // add the final evaluation page + evaluationPage = new RiskWizardPageEvaluation(questionnaire, controller); + this.addPage(evaluationPage); + } + + /** + * Returns the controller + * @return + */ + public Controller getController() { + return this.controller; + } + + /** + * Returns the questionnaire + * @return + */ + public RiskQuestionnaire getQuestionnaire() { + return this.questionnaire; + } + + /** + * Called when the dialog is finished, saves the current weights + */ + @Override + public boolean performFinish() { + this.controller.getModel().setRiskQuestionnaireWeights(this.questionnaire.getWeights()); + return true; + } + + /** + * Updates the weights for each section and the evaluation + */ + protected void updateWeights() { + for (RiskWizardPageSection page : pages) { + page.updateWeights(); + } + evaluationPage.updateWeights(); + } +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentAnswer.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentAnswer.java new file mode 100644 index 000000000..147fde15f --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentAnswer.java @@ -0,0 +1,88 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.*; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.*; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaireQuestion; +import org.deidentifier.arx.risk.RiskQuestionnaireQuestion.*; + +/** + * The radio group component for answering the questions, contains yes, no and + * n/a as possible answers + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardComponentAnswer extends Composite { + + /** Widget */ + private Button yesButton; + /** Widget */ + private Button noButton; + /** Widget */ + private Button n_aButton; + /** The question which will be updated, when the selected button changes */ + private RiskQuestionnaireQuestion item; + + /** + * create a new answer radio group in the specified composite and targetItem + * + * @param parent + * @param targetItem + */ + public RiskWizardComponentAnswer(Composite parent, RiskQuestionnaireQuestion targetItem) { + super(parent, SWT.NONE); + this.item = targetItem; + this.setLayout(new FillLayout()); + Group group = new Group(this, SWT.SHADOW_IN); + group.setLayout(new RowLayout(SWT.HORIZONTAL)); + + SelectionListener selectionListener = new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent event) { + Button button = ((Button) event.widget); + if (button == yesButton) { + item.answer = Answer.YES; + } else if (button == noButton) { + item.answer = Answer.NO; + } else { + item.answer = Answer.N_A; + } + }; + }; + + yesButton = new Button(group, SWT.RADIO); + yesButton.setText(Resources.getMessage("RiskWizard.3")); + yesButton.addSelectionListener(selectionListener); + + noButton = new Button(group, SWT.RADIO); + noButton.setText(Resources.getMessage("RiskWizard.4")); + noButton.addSelectionListener(selectionListener); + + n_aButton = new Button(group, SWT.RADIO); + n_aButton.setText(Resources.getMessage("RiskWizard.5")); + n_aButton.setSelection(true); + n_aButton.addSelectionListener(selectionListener); + } + +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentWeight.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentWeight.java new file mode 100644 index 000000000..b5f898a6b --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardComponentWeight.java @@ -0,0 +1,145 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import java.text.DecimalFormat; +import java.text.ParseException; + +import org.deidentifier.arx.risk.RiskQuestionnaireItem; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; + +/** + * A drop down field for changing a question's weight + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardComponentWeight { + + /** The targeted item */ + private RiskQuestionnaireItem item; + /** Widget */ + private Combo dropdown; + /** Flag */ + private boolean disableUpdateQuestion; + /** Flag */ + private boolean enabled; + /** Format */ + static final DecimalFormat df = new DecimalFormat("#.00"); + + /** + * create a new weight field for an item (question or section) + * + * @param composite + * the composite + * @param item + * the item this field targets + * @param enabled + * whether this control is currently enabled + */ + public RiskWizardComponentWeight(Composite composite, RiskQuestionnaireItem item, boolean enabled) { + dropdown = new Combo(composite, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); + dropdown.add("0.25"); + dropdown.add("0.50"); + dropdown.add("1.00"); + dropdown.add("1.50"); + dropdown.add("2.00"); + dropdown.add("3.00"); + dropdown.add("5.00"); + + ModifyListener modifyListener = new ModifyListener() { + @Override + public void modifyText(ModifyEvent e) { + RiskWizardComponentWeight.this.updateItem(); + } + }; + + dropdown.addModifyListener(modifyListener); + + this.item = item; + this.setEnabled(enabled); + + GridData gridData = new GridData(GridData.VERTICAL_ALIGN_CENTER); + gridData.widthHint = 72; + dropdown.setLayoutData(gridData); + + updateText(); + } + + /** + * returns, whether the field is editable + * + * @return if field can be edited + */ + public boolean isEnabled() { + return enabled; + } + + /** + * change whether field can be edited + * + * @param enabled + * whether field can be edited + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + this.dropdown.setEnabled(enabled); + } + + /** + * updates the text of the dropdown + */ + public void updateText() { + disableUpdateQuestion = true; + double newWeight = item.getWeight(); + String weightString = df.format(newWeight); + // System.out.println("Weight: "+weightString+" item="+item.getIdentifier()); + + boolean containsText = false; + for (String t : dropdown.getItems()) { + if (t.equals(weightString)) { + containsText = true; + } + } + if (containsText == false) { + dropdown.add(weightString); + } + + dropdown.setText(df.format(newWeight)); + disableUpdateQuestion = false; + } + + /** + * updates the target item + */ + protected void updateItem() { + if (disableUpdateQuestion) { return; } + try { + double value = df.parse(dropdown.getText()).doubleValue(); + item.setWeight(value); + } catch (ParseException e) { + // e.printStackTrace(); + } + } + +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardDialog.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardDialog.java new file mode 100644 index 000000000..5e0d2133c --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardDialog.java @@ -0,0 +1,225 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.util.Properties; + +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.deidentifier.arx.risk.RiskQuestionnaireWeights; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.jface.wizard.ProgressMonitorPart; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; + +/** + * The ChecklistDialog is the dialog presented for the wizard + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardDialog extends WizardDialog { + + /** Widget */ + private Button weightEditButton; + + /** Widget */ + private Button loadButton; + + /** Widget */ + private Button saveButton; + + /** Model */ + private RiskQuestionnaire questionnaire; + + /** Controller */ + private Controller controller; + + /** + * creates a new checklist dialog for a specified checklist + * + * @param checklist the checklist to use + * @param parentShell the parent for this dialog + * @param controller the arx controller + * @param newWizard the wizard + */ + public RiskWizardDialog(RiskWizard wizard) { + super(wizard.getShell(), wizard); + this.questionnaire = wizard.getQuestionnaire(); + this.controller = wizard.getController();; + } + + /** + * create a custom button bar, with the load/store/edit buttons for the + * weight profiles + */ + @Override + protected void createButtonsForButtonBar(Composite parent) { + // this code creates the button on the left side (settings) + GridLayout layout = (GridLayout) parent.getLayout(); + layout.horizontalSpacing = 0; + + // adjust the parent to use the full width + GridData gridData = (GridData) parent.getLayoutData(); + gridData.grabExcessHorizontalSpace = true; + gridData.minimumWidth = 650; + gridData.horizontalAlignment = GridData.FILL; + + // add the settings button + layout.numColumns++; + + weightEditButton = new Button(parent, SWT.CHECK); + weightEditButton.setText(Resources.getMessage("RiskWizard.6")); + + final RiskWizardDialog reference = this; + + loadButton = new Button(parent, SWT.PUSH); + loadButton.setText(Resources.getMessage("RiskWizard.7")); + loadButton.setVisible(false); + loadButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog dialog = new FileDialog(loadButton.getShell(), SWT.OPEN); + dialog.setFilterExtensions(new String[] { "*.txt", "*.properties", "*.weights" }); + dialog.setFilterPath("config/weights"); + String result = dialog.open(); + if (result != null) { + updateWeightConfig(result); + } + } + }); + layout.numColumns++; + + saveButton = new Button(parent, SWT.PUSH); + saveButton.setText(Resources.getMessage("RiskWizard.8")); + saveButton.setVisible(false); + saveButton.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + FileDialog dialog = new FileDialog(saveButton.getShell(), SWT.SAVE); + dialog.setFileName("weights.txt"); + dialog.setFilterExtensions(new String[] { "*.txt", "*.properties", "*.weights" }); + dialog.setFilterPath("config/weights"); + String result = dialog.open(); + if (result != null) { + try { + questionnaire.getWeights().asProperties().store(new FileOutputStream(result), null); + } catch (Exception exception) { + controller.actionShowInfoDialog(getShell(), Resources.getMessage("Controller.13"), Resources.getMessage("RiskWizard.20")); + } + } + } + }); + layout.numColumns++; + + Listener listener = new Listener() { + @Override + public void handleEvent(Event event) { + reference.setWeightsEditable(weightEditButton.getSelection()); + } + }; + + weightEditButton.addListener(SWT.Selection, listener); + + // Add a placeholder label that uses the empty space + layout.numColumns++; + Label placeholder = new Label(parent, 1); + placeholder.setText(""); + placeholder.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); + + super.createButtonsForButtonBar(parent); + } + + @Override + protected Control createDialogArea(Composite parent) { + Control ctrl = super.createDialogArea(parent); + getProgressMonitor(); + return ctrl; + } + + @Override + protected IProgressMonitor getProgressMonitor() { + // remove progress monitor, taken from + // http://commercialjavaproducts.blogspot.de/2010/11/remove-progress-monitor-part-from-jface.html + ProgressMonitorPart monitor = (ProgressMonitorPart) super.getProgressMonitor(); + GridData gridData = new GridData(GridData.FILL_HORIZONTAL); + gridData.heightHint = 0; + monitor.setLayoutData(gridData); + monitor.setVisible(false); + return monitor; + } + + /** + * enable or disable the edit mode + * + * @param weightsEditable + * whether the weights should be changeable + */ + protected void setWeightsEditable(boolean weightsEditable) { + loadButton.setVisible(weightsEditable); + saveButton.setVisible(weightsEditable); + IWizardPage pages[] = this.getWizard().getPages(); + for (IWizardPage page : pages) { + if (page instanceof RiskWizardPageSection) { + RiskWizardPageSection sectionPage = (RiskWizardPageSection) page; + sectionPage.setWeightEditable(weightsEditable); + } + } + this.dialogArea.update(); + } + + /** + * updates the current weight configuration + * + * @param result + * the weight configuration to load + */ + protected void updateWeightConfig(String result) { + try { + RiskQuestionnaireWeights weights = new RiskQuestionnaireWeights(); + Properties properties = new Properties(); + properties.load(new FileInputStream(new File(result))); + weights.loadFromProperties(properties); + questionnaire.setWeights(weights); + IWizard wizard = this.getWizard(); + if (wizard instanceof RiskWizard) { + RiskWizard casted = (RiskWizard) wizard; + casted.updateWeights(); + } + } catch (Exception e) { + controller.actionShowInfoDialog(getShell(), Resources.getMessage("Controller.13"), Resources.getMessage("RiskWizard.21")); + } + } +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageEvaluation.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageEvaluation.java new file mode 100644 index 000000000..6ac92b20e --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageEvaluation.java @@ -0,0 +1,208 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +/** + * Final page containing the two different visualizations + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardPageEvaluation extends WizardPage { + + /** The checklist */ + private RiskQuestionnaire checklist; + /** The current visualization */ + private RiskWizardVisualization visualization; + /** Widget */ + private Composite rootComposite; + /** Widget */ + private Controller controller; + + /** + * create the evaluation page for the checklist + * + * @param checklist + * the checklist to use + * @param controller + * the arx controller + */ + protected RiskWizardPageEvaluation(RiskQuestionnaire checklist, Controller controller) { + super(Resources.getMessage("RiskWizard.9")); + + this.checklist = checklist; + this.controller = controller; + this.setTitle(Resources.getMessage("RiskWizard.10")); + this.setDescription(Resources.getMessage("RiskWizard.11")); + } + + /** + * creates the control and adds the visualization selection top bar + */ + @Override + public void createControl(Composite parent) { + GridLayout layout = new GridLayout(); + layout.numColumns = 1; + layout.marginHeight = 0; + layout.marginTop = 0; + layout.marginBottom = 0; + layout.verticalSpacing = 0; + layout.makeColumnsEqualWidth = true; + + rootComposite = new Composite(parent, SWT.NO_BACKGROUND); + rootComposite.setLayout(layout); + GridData rootData = new GridData(); + rootData.grabExcessHorizontalSpace = true; + rootData.horizontalAlignment = GridData.FILL; + rootData.grabExcessVerticalSpace = true; + rootData.verticalAlignment = GridData.FILL; + rootComposite.setLayoutData(rootData); + + createTopBar(rootComposite, layout.numColumns); + + this.showMonitorVisualization(); + + setControl(rootComposite); + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) { + this.updateWeights(); + } + } + + /** + * creates the top bar used for switching between visualizations + * + * @param parent + * the parent composite + * @param span + * the span to use for the bar + * @return + */ + private Composite createTopBar(Composite parent, int span) { + Composite c = new Composite(parent, SWT.NO_BACKGROUND); + + GridData cData = new GridData(); + cData.horizontalSpan = span; + cData.horizontalAlignment = GridData.FILL; + c.setLayoutData(cData); + + GridLayout layout = new GridLayout(); + layout.numColumns = 3; + c.setLayout(layout); + + Label weightLabel = new Label(c, SWT.LEFT); + GridData weightData = new GridData(); + weightData.grabExcessHorizontalSpace = true; + weightLabel.setLayoutData(weightData); + weightLabel.setText(Resources.getMessage("RiskWizard.19")); + FontDescriptor boldDescriptor = FontDescriptor.createFrom(weightLabel.getFont()) + .setStyle(SWT.BOLD); + Font boldFont = boldDescriptor.createFont(weightLabel.getDisplay()); + weightLabel.setFont(boldFont); + + final Combo visualizationDropDown = new Combo(c, SWT.DROP_DOWN | SWT.READ_ONLY | SWT.BORDER); + final String monitorTitle = Resources.getMessage("RiskWizard.12"); + final String stacksTitle = Resources.getMessage("RiskWizard.13"); + visualizationDropDown.add(monitorTitle); + visualizationDropDown.add(stacksTitle); + visualizationDropDown.setText(monitorTitle); + visualizationDropDown.addSelectionListener(new SelectionAdapter() { + @Override + public void widgetSelected(SelectionEvent e) { + String selected = visualizationDropDown.getText(); + if (selected.equals(stacksTitle)) { + showStacksVisualization(); + } else if (selected.equals(monitorTitle)) { + showMonitorVisualization(); + } + } + }); + + Label separator = new Label(c, SWT.SEPARATOR | SWT.HORIZONTAL); + GridData sepData = new GridData(); + sepData.horizontalSpan = layout.numColumns; + sepData.grabExcessHorizontalSpace = true; + sepData.horizontalAlignment = GridData.FILL; + separator.setLayoutData(sepData); + + return c; + } + + /** + * remove the current visualization from the UI + */ + private void removeVisualization() { + if (this.visualization != null) { + this.visualization.dispose(); + } + } + + /** + * set the current visualization and update the UI + * + * @param visualization + * the visualization to change to + */ + private void setVisualization(RiskWizardVisualization visualization) { + removeVisualization(); + this.visualization = visualization; + updateWeights(); + this.rootComposite.layout(); + } + + /** + * change to monitor visualization + */ + private void showMonitorVisualization() { + setVisualization(new RiskWizardVisualizationMonitor(rootComposite, controller, checklist)); + } + + /** + * change to stacks visualization + */ + private void showStacksVisualization() { + setVisualization(new RiskWizardVisualizationStack(rootComposite, controller, checklist)); + } + + /** + * update the current visualization when the weights change + */ + protected void updateWeights() { + visualization.updateWeights(); + } + +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageSection.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageSection.java new file mode 100644 index 000000000..a9d077d3b --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardPageSection.java @@ -0,0 +1,180 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Listener; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaireQuestion; +import org.deidentifier.arx.risk.RiskQuestionnaireSection; + +/** + * Each SectionPage shows all the questions from a checklist section + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardPageSection extends WizardPage { + + /** Widget */ + private RiskQuestionnaireSection section; + /** Widget */ + private Composite container; + /** Field */ + private boolean weightEditable; + /** Field */ + private List weightFields; + + /** + * create a new page for a section + * + * @param section + * the section of this page + */ + public RiskWizardPageSection(RiskQuestionnaireSection section) { + super(section.getTitle()); + + this.weightEditable = false; + this.section = section; + this.setTitle(section.getTitle()); + this.setDescription(Resources.getMessage("RiskWizard.1")); + } + + /** + * creates the control, by adding the questions inside a scroll view + */ + @Override + public void createControl(Composite parent) { + final Composite rootComposite = new Composite(parent, SWT.NONE); + + GridLayout rootGrid = new GridLayout(); + rootComposite.setLayout(rootGrid); + + final ScrolledComposite sc = new ScrolledComposite(rootComposite, SWT.BORDER | SWT.V_SCROLL); + GridData sgd = new GridData(GridData.FILL_BOTH); + sgd.grabExcessHorizontalSpace = true; + sgd.grabExcessVerticalSpace = true; + sgd.widthHint = 400;// SWT.DEFAULT; + sgd.heightHint = 300; + sc.setLayoutData(sgd); + + sc.setExpandHorizontal(true); + sc.setExpandVertical(true); + + container = new Composite(sc, SWT.NULL); + GridLayout layout = new GridLayout(); + container.setLayout(layout); + layout.numColumns = 3; + layout.verticalSpacing = 12; + + createItems(); + + rootComposite.addListener(SWT.Resize, new Listener() { + int width = -1; + + @Override + public void handleEvent(org.eclipse.swt.widgets.Event e) { + int newWidth = rootComposite.getSize().x; + if (newWidth != width) { + sc.setMinHeight(container.computeSize(newWidth, SWT.DEFAULT).y + 40); + width = newWidth; + } + } + }); + + sc.setContent(container); + sc.setMinSize(container.computeSize(400, SWT.DEFAULT)); + sc.layout(); + + setControl(rootComposite); + } + + /** + * enable or disable the weight edit mode + */ + public void setWeightEditable(boolean editable) { + if (editable == this.weightEditable) { return; } + this.weightEditable = editable; + + // TODO iterate and set + for (RiskWizardComponentWeight w : this.weightFields) { + w.setEnabled(editable); + } + } + + /** + * creates the interface for the individual items (questions) + */ + private void createItems() { + // add weight fields + this.weightFields = new ArrayList(); + this.weightFields.add(new RiskWizardComponentWeight(container, this.section, false)); + + Label label = new Label(container, SWT.NONE | SWT.WRAP); + label.setText(this.section.getTitle()); + // from + // http://eclipsesource.com/blogs/2014/02/10/swt-best-practices-changing-fonts/ + FontDescriptor boldDescriptor = FontDescriptor.createFrom(label.getFont()) + .setStyle(SWT.BOLD); + Font boldFont = boldDescriptor.createFont(label.getDisplay()); + label.setFont(boldFont); + + GridData headerGridData = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + headerGridData.verticalAlignment = GridData.CENTER; + headerGridData.grabExcessVerticalSpace = false; + headerGridData.horizontalSpan = 2; + label.setLayoutData(headerGridData); + + RiskQuestionnaireQuestion[] items = section.getItems(); + for (int i = 0; i < items.length; i++) { + RiskQuestionnaireQuestion itm = items[i]; + + this.weightFields.add(new RiskWizardComponentWeight(container, itm, false)); + + Label label1 = new Label(container, SWT.NONE | SWT.WRAP); + label1.setText(itm.getTitle()); + GridData gd = new GridData(GridData.FILL_HORIZONTAL | GridData.FILL_VERTICAL); + gd.verticalAlignment = GridData.CENTER; + gd.grabExcessVerticalSpace = false; + label1.setLayoutData(gd); + + new RiskWizardComponentAnswer(container, itm); + } + } + + /** + * update the weights (called when a new weight configuration is loaded) + */ + protected void updateWeights() { + if (this.weightFields == null) { return; } + for (RiskWizardComponentWeight field : weightFields) { + field.updateText(); + } + } +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualization.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualization.java new file mode 100644 index 000000000..c6dd99a8a --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualization.java @@ -0,0 +1,73 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.risk.RiskQuestionnaire; + +/** + * Base class for the visualizations + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public abstract class RiskWizardVisualization extends Composite { + + /** Checklist used for the visualization */ + protected RiskQuestionnaire checklist; + /** Controller */ + protected Controller controller; + + /** + * Create a new visualization for the checklist + * + * @param parent + * the parent + * @param controller + * the controller + * @param checklist + * the checklist + */ + public RiskWizardVisualization(Composite parent, Controller controller, RiskQuestionnaire checklist) { + super(parent, SWT.NO_SCROLL); + + GridData gridData = new GridData(); + gridData.grabExcessHorizontalSpace = true; + gridData.grabExcessVerticalSpace = true; + gridData.horizontalAlignment = SWT.FILL; + gridData.verticalAlignment = SWT.FILL; + this.setLayoutData(gridData); + + this.checklist = checklist; + this.controller = controller; + this.createVisualization(); + } + + /** + * Used in subclasses to respond to changes + */ + public abstract void updateWeights(); + + /** + * Used in subclasses for the initial setup + */ + protected abstract void createVisualization(); +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationMonitor.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationMonitor.java new file mode 100644 index 000000000..d021d78e0 --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationMonitor.java @@ -0,0 +1,120 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import java.util.ArrayList; +import java.util.List; + +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.gui.view.SWTUtil; +import org.deidentifier.arx.gui.view.impl.common.ComponentRiskMonitor; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.deidentifier.arx.risk.RiskQuestionnaireSection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; + +/** + * The monitor visualization + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardVisualizationMonitor extends RiskWizardVisualization { + + /** Contains the monitors for each section */ + private List monitors; + + /** Overall monitor, showing the overall score */ + private ComponentRiskMonitor totalMonitor; + + /** + * Create a new monitor visualization + * + * @param parent + * the parent + * @param controller + * the controller + * @param checklist + * the checklist to use + */ + public RiskWizardVisualizationMonitor(Composite parent, + Controller controller, + RiskQuestionnaire checklist) { + super(parent, controller, checklist); + } + + /** + * Update the UI when the weights change + */ + @Override + public void updateWeights() { + RiskQuestionnaireSection sections[] = checklist.getSections(); + for (int i = 0; i < sections.length; i++) { + RiskQuestionnaireSection s = sections[i]; + + ComponentRiskMonitor riskMonitor = monitors.get(i); + riskMonitor.setRisk(1.0 - ((s.getScore() / 2.0) + 0.5)); + } + + totalMonitor.setRisk(1.0 - ((checklist.getScore() / 2.0) + 0.5)); + } + + /** + * Creates the view containing the different monitors + */ + @Override + protected void createVisualization() { + RiskQuestionnaireSection sections[] = this.checklist.getSections(); + monitors = new ArrayList(); + + GridLayout layout = SWTUtil.createGridLayoutWithEqualWidth(sections.length); + layout.marginHeight = 0; + layout.marginTop = 0; + layout.marginBottom = 0; + layout.makeColumnsEqualWidth = true; + this.setLayout(layout); + + for (int i = 0; i < sections.length; i++) { + String title = sections[i].getTitle(); + + ComponentRiskMonitor riskMonitor = new ComponentRiskMonitor(this, + this.controller, + title, + title); + riskMonitor.setLayoutData(SWTUtil.createFillGridData()); + riskMonitor.setRisk(0.5); + monitors.add(riskMonitor); + } + + String totalTitle = Resources.getMessage("RiskWizard.2"); + totalMonitor = new ComponentRiskMonitor(this, this.controller, totalTitle, totalTitle); + totalMonitor.setLayoutData(SWTUtil.createFillGridData()); + totalMonitor.setRisk(0.5); + + GridData gridData = new GridData(); + gridData.grabExcessVerticalSpace = true; + gridData.horizontalAlignment = SWT.FILL; + gridData.verticalAlignment = SWT.FILL; + gridData.minimumHeight = 150; + gridData.horizontalSpan = layout.numColumns; + totalMonitor.setLayoutData(gridData); + } +} diff --git a/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationStack.java b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationStack.java new file mode 100644 index 000000000..c31046928 --- /dev/null +++ b/src/gui/org/deidentifier/arx/gui/view/impl/wizard/RiskWizardVisualizationStack.java @@ -0,0 +1,167 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.gui.view.impl.wizard; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.swtchart.*; +import org.swtchart.ISeries.*; +import org.deidentifier.arx.gui.Controller; +import org.deidentifier.arx.gui.resources.Resources; +import org.deidentifier.arx.risk.RiskQuestionnaire; +import org.deidentifier.arx.risk.RiskQuestionnaireQuestion; +import org.deidentifier.arx.risk.RiskQuestionnaireSection; + +/** + * The stack visualization + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskWizardVisualizationStack extends RiskWizardVisualization { + + /** Displayed chart */ + private Chart chart; + + /** Bar series for the positive values */ + private IBarSeries positive; + + /** Bar series for the neutral values */ + private IBarSeries neutral; + + /** Bar series for the negative values */ + private IBarSeries negative; + + /** + * Create a new stack visualization + * + * @param parent + * the parent + * @param controller + * the controller + * @param checklist + * the checklist + */ + public RiskWizardVisualizationStack(Composite parent, Controller controller, RiskQuestionnaire checklist) { + super(parent, controller, checklist); + } + + /** + * Updates the UI when the weights/score changes + */ + @Override + public void updateWeights() { + RiskQuestionnaireSection sections[] = this.checklist.getSections(); + double[] posY = new double[sections.length]; + double[] neuY = new double[sections.length]; + double[] negY = new double[sections.length]; + + int idx = 0; + for (RiskQuestionnaireSection sec : sections) { + double pos = 0.0d; + double neu = 0.0d; + double neg = 0.0d; + double max = 0.0d; + for (RiskQuestionnaireQuestion q : sec.getItems()) { + double w = q.getWeight(); + double s = q.getScore(); + max += w; + if (s == 0.0) { + neu += w; + } else if (s > 0.0) { + pos += w; + } else { + neg += w; + } + } + + posY[idx] = pos / max; + neuY[idx] = neu / max; + negY[idx] = neg / max; + + idx++; + } + + positive.setYSeries(posY); + positive.enableStack(true); + neutral.setYSeries(neuY); + neutral.enableStack(true); + negative.setYSeries(negY); + negative.enableStack(true); + + chart.update(); + chart.redraw(); + } + + /** + * Creates the view containing the stack bar graph visualization + */ + @Override + protected void createVisualization() { + RiskQuestionnaireSection sections[] = this.checklist.getSections(); + String sectionNames[] = new String[sections.length]; + int idx = 0; + for (RiskQuestionnaireSection s : sections) { + sectionNames[idx] = s.getTitle(); + idx++; + } + + FillLayout fillLayout = new FillLayout(); + fillLayout.type = SWT.VERTICAL; + this.setLayout(fillLayout); + chart = new Chart(this, SWT.NONE); + chart.getTitle().setText(Resources.getMessage("RiskWizard.18")); + + double[] positiveSeries = { 0.1, 0, 0 }; + double[] neutralSeries = { 0.1, 0, 0 }; + double[] negativeSeries = { 0.1, 0, 0 }; + + ISeriesSet seriesSet = chart.getSeriesSet(); + + positive = (IBarSeries) seriesSet.createSeries(SeriesType.BAR, + Resources.getMessage("RiskWizard.15")); + positive.setBarColor(this.getDisplay().getSystemColor(SWT.COLOR_GREEN)); + positive.enableStack(true); + positive.setYSeries(positiveSeries); + + neutral = (IBarSeries) seriesSet.createSeries(SeriesType.BAR, + Resources.getMessage("RiskWizard.16")); + neutral.setBarColor(this.getDisplay().getSystemColor(SWT.COLOR_GRAY)); + neutral.enableStack(true); + neutral.setYSeries(neutralSeries); + + negative = (IBarSeries) seriesSet.createSeries(SeriesType.BAR, + Resources.getMessage("RiskWizard.17")); + negative.setBarColor(this.getDisplay().getSystemColor(SWT.COLOR_RED)); + negative.enableStack(true); + negative.setYSeries(negativeSeries); + + IAxisSet axisSet = chart.getAxisSet(); + axisSet.adjustRange(); + + IAxis yAxis = axisSet.getYAxis(0); + yAxis.setRange(new Range(0.0, 1.05)); + yAxis.getTitle().setVisible(false); + + IAxis xAxis = axisSet.getXAxis(0); + xAxis.setCategorySeries(sectionNames); + xAxis.enableCategory(true); + xAxis.getTitle().setVisible(false); + } +} diff --git a/src/main/org/deidentifier/arx/risk/RiskQuestionnaire.java b/src/main/org/deidentifier/arx/risk/RiskQuestionnaire.java new file mode 100644 index 000000000..3e94c639f --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/RiskQuestionnaire.java @@ -0,0 +1,160 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.deidentifier.arx.risk; + +import java.io.BufferedReader; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + + +/** + * The questionnaire holds the sections and calculates the overall score + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskQuestionnaire implements Serializable { + + /** SVUID */ + private static final long serialVersionUID = 5949613671782771835L; + + /** The array containing the sections of the questionnaire */ + private List sections = new ArrayList<>(); + + /** + * Create a questionnaire + * + * @param data + * @throws IOException + */ + public RiskQuestionnaire(HIPAAConstants data) throws IOException { + load(new BufferedReader(new InputStreamReader(data.getInputStream("risk-questionnaire.data")))); + } + + /** + * Get the current score of the complete checklist + * + * @return the score + */ + public double getScore() { + + double result = 0.0; + double max = 0d; + for (RiskQuestionnaireSection s : sections) { + result += s.getScore(); + max += s.getWeight(); + } + result /= max; + return result; + } + + /** + * Get all sections + * + * @return the sections + */ + public RiskQuestionnaireSection[] getSections() { + return (sections.toArray(new RiskQuestionnaireSection[sections.size()])); + } + + /** + * Returns the current weights + */ + public RiskQuestionnaireWeights getWeights() { + RiskQuestionnaireWeights weights = new RiskQuestionnaireWeights(); + for (RiskQuestionnaireSection section : this.sections) { + weights.setWeight(section.getIdentifier(), section.getWeight()); + for (RiskQuestionnaireItem item : section.getItems()) { + weights.setWeight(section.getIdentifier()+":"+item.getIdentifier(), item.getWeight()); + } + } + return weights; + } + + /** + * Sets the weights + * @param weights + */ + public void setWeights(RiskQuestionnaireWeights weights) { + for (RiskQuestionnaireSection section : this.sections) { + section.setWeights(weights); + } + } + + /** + * Loads the checklist using a buffered reader + * + * @param bufferedReader + * @throws IOException + */ + private void load(BufferedReader bufferedReader) throws IOException { + + try { + + // Hold a reference to the current section + RiskQuestionnaireSection currentSection = null; + + // Read first line, then iterate over the lines + String line = bufferedReader.readLine(); + while (line != null) { + + // Get rid of leading/trailing whitespaces + line = line.trim(); + if (line.length() == 0) { + + // Ignore + + } else if (line.startsWith("#")) { + + // Current line is a section + currentSection = new RiskQuestionnaireSection(line.substring(1)); + sections.add(currentSection); + + } else { + + // Current line is an item + if (currentSection == null) { + throw new IOException("Invalid questionnaire specification"); + } + + // parse and add item + currentSection.addItem(new RiskQuestionnaireQuestion(line)); + } + + // read next line + line = bufferedReader.readLine(); + } + + } catch (FileNotFoundException e) { + throw(e); + } catch (IOException e) { + throw(e); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException e) { + // Ignore + } + } + } + } +} diff --git a/src/main/org/deidentifier/arx/risk/RiskQuestionnaireItem.java b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireItem.java new file mode 100644 index 000000000..9d45a3b48 --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireItem.java @@ -0,0 +1,105 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.risk; + +import java.io.IOException; +import java.io.Serializable; + +/** + * This is the base class for the Question as well as the Section. + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public abstract class RiskQuestionnaireItem implements Serializable { + + /** SVUID */ + private static final long serialVersionUID = -9134631374669047761L; + + /** Identifier (used for the weight configuration) */ + private String identifier = null; + + /** Item's title */ + private String title = null; + + /** Weight */ + private double weight = 1d; + + /** + * Creates a new instance + * @param line + * @throws IOException + */ + public RiskQuestionnaireItem(String line) throws IOException { + parse(line); + } + + /** + * Returns the identifier + * + * @return + */ + public String getIdentifier() { + return identifier; + } + + /** + * Returns the title + * + * @return + */ + public String getTitle() { + return title; + } + + /** + * Gets the current weight for this item according to the current weight + * configuration + * + * @return + */ + public double getWeight() { + return this.weight; + } + + /** + * Updates the weight configurations weight for this item + * + * @param weight + */ + public void setWeight(double weight) { + this.weight = weight; + } + + /** + * Parse the item + * @param line + * @throws IOException + */ + private void parse(String line) throws IOException { + if (line == null) { + throw new IOException("Invalid questionnaire definition: " + line); + } + String components[] = line.split(":", 2); + if (components == null ||components.length != 2) { + throw new IOException("Invalid questionnaire definition: " + line); + } + this.identifier = components[0].trim(); + this.title = components[1].trim(); + } +} diff --git a/src/main/org/deidentifier/arx/risk/RiskQuestionnaireQuestion.java b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireQuestion.java new file mode 100644 index 000000000..6a845c8a4 --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireQuestion.java @@ -0,0 +1,90 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.risk; + +import java.io.IOException; +import java.io.Serializable; + +import org.deidentifier.arx.gui.resources.Resources; + +/** + * Represents a single question from the checklist + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskQuestionnaireQuestion extends RiskQuestionnaireItem implements Serializable { + + /** Enum for answers */ + public enum Answer { + YES, + NO, + N_A + } + + /** SVUID*/ + private static final long serialVersionUID = 1342060103957413041L; + + /** Current answer */ + public Answer answer; + + /** + * Creates a new question in a section, from a line + * + * @param section + * @param line + * @throws IOException + */ + public RiskQuestionnaireQuestion(String line) throws IOException { + super(line.trim()); + this.answer = Answer.N_A; + } + + /** + * Get the current answer as a string + * + * @return the string + */ + public String getAnswerString() { + switch (answer) { + case YES: + return Resources.getMessage("RiskWizard.3"); + case NO: + return Resources.getMessage("RiskWizard.4"); + default: + return Resources.getMessage("RiskWizard.5"); + } + } + + /** + * Returns the question's score using the weight and taking the answer into + * account + * + * @return the score + */ + public double getScore() { + switch (answer) { + case YES: + return this.getWeight(); + case NO: + return -this.getWeight(); + default: + return 0.0; + } + } +} diff --git a/src/main/org/deidentifier/arx/risk/RiskQuestionnaireSection.java b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireSection.java new file mode 100644 index 000000000..3e5bfaf58 --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireSection.java @@ -0,0 +1,101 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.risk; + +import java.io.IOException; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +/** + * Represents a section from the checklist + * + * @author Thomas Guenzel + * @author Fabian Prasser + */ +public class RiskQuestionnaireSection extends RiskQuestionnaireItem implements Serializable { + + /** SVUID*/ + private static final long serialVersionUID = -6574573674768245518L; + + /** The items this section contains */ + private List items = new ArrayList<>(); + + /** + * Create a new section from a line + * + * @param weightConfiguration the current weight configuration + * @param line the line to parse + * @throws IOException + */ + public RiskQuestionnaireSection(String line) throws IOException { + super(line.trim()); + this.items = new ArrayList(); + } + + /** + * Add a question to the section and updates the maximumWeight + * + * @param item the question to add + */ + public void addItem(RiskQuestionnaireQuestion item) { + items.add(item); + } + + /** + * Get all questions in this section + * + * @return + */ + public RiskQuestionnaireQuestion[] getItems() { + return items.toArray(new RiskQuestionnaireQuestion[items.size()]); + } + + /** + * Calculates the score based on the section's question's scores + * + * @return + */ + public double getScore() { + + double result = 0d; + double max = 0d; + for (RiskQuestionnaireQuestion q : items) { + result += q.getScore(); + max += q.getWeight(); + } + result /= max; + return result; + } + + /** + * Set weights + */ + public void setWeights(RiskQuestionnaireWeights weights) { + Double weight = weights.getWeight(this.getIdentifier()); + if (weight != null) { + this.setWeight(weight); + } + for (RiskQuestionnaireQuestion item : this.items) { + weight = weights.getWeight(this.getIdentifier()+":"+item.getIdentifier()); + if (weight != null) { + item.setWeight(weight); + } + } + } +} diff --git a/src/main/org/deidentifier/arx/risk/RiskQuestionnaireWeights.java b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireWeights.java new file mode 100644 index 000000000..8a005211e --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/RiskQuestionnaireWeights.java @@ -0,0 +1,87 @@ +/* + * ARX: Powerful Data Anonymization + * Copyright 2012 - 2017 Fabian Prasser, Florian Kohlmayer and contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.deidentifier.arx.risk; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Properties; + +/** + * The weight configuration used when evaluating the checklist + * + */ +public class RiskQuestionnaireWeights implements Serializable { + + /** SVUID */ + private static final long serialVersionUID = -7601091333545929290L; + + /** Map, mapping the item identifiers to a weight */ + private Map weights = new HashMap(); + + /** + * Returns properties + * + */ + public Properties asProperties() { + Properties props = new Properties(); + for (Entry entry : weights.entrySet()) { + props.setProperty(entry.getKey(), Double.toString(entry.getValue())); + } + return props; + } + + /** + * Get the weight for an item's identifier + * + * @param identifier + * @return the weight + */ + public double getWeight(String identifier) { + if (weights.containsKey(identifier) == false) { return 1.0; } + return weights.get(identifier); + } + + /** + * Load weights + * @param properties + */ + public void loadFromProperties(Properties properties) { + try { + for (Entry entry : properties.entrySet()) { + String key = (String) entry.getKey(); + String value = (String) entry.getValue(); + double weight = Double.parseDouble(value); + weights.put(key, weight); + } + } catch (Exception e) { + throw new IllegalArgumentException("Invalid properties"); + } + } + + /** + * Sets the weight for an item's identifier + * + * @param identifier + * @param weight + */ + public void setWeight(String identifier, double weight) { + weights.put(identifier, weight); + } +} diff --git a/src/main/org/deidentifier/arx/risk/resources/us/readme.txt b/src/main/org/deidentifier/arx/risk/resources/us/readme.txt index 0e3372154..332614449 100644 --- a/src/main/org/deidentifier/arx/risk/resources/us/readme.txt +++ b/src/main/org/deidentifier/arx/risk/resources/us/readme.txt @@ -29,4 +29,10 @@ lastnames.csv zip.csv *********** -(1) http://simplemaps.com/resources/us-cities-data \ No newline at end of file +(1) http://simplemaps.com/resources/us-cities-data + +************** +Questionnaire +************** + +K. E. Emam, “De-identifying health data for secondary use: A framework,” Technical Report, CHEO Research Institute, 2008. \ No newline at end of file diff --git a/src/main/org/deidentifier/arx/risk/resources/us/risk-questionnaire.data b/src/main/org/deidentifier/arx/risk/resources/us/risk-questionnaire.data new file mode 100644 index 000000000..a13247bcb --- /dev/null +++ b/src/main/org/deidentifier/arx/risk/resources/us/risk-questionnaire.data @@ -0,0 +1,38 @@ +#mitigating: Mitigating Controls + sharing-forbids: The data sharing agreement forbids the recipient from disclosing the database to third parties + sharing-enforceable: The data sharing agreement is enforceable in all jurisdictions where the recipient will use the data + sharing-audits: The data sharing agreement will allow surprise audits of the recipient's record management system and practices + sharing-dblimits: The data sharing agreement imposes strong limits linking the database with other administrative or clinical data sources + privacy-policy: The recipient has a written privacy policy + privacy-on-site: There is a person responsible for privacy at the recipient's site + staff-confidential: All members of the team on the recipient site have signed a confidentiality agreement as a condition of them having access to the disclosed database + recipient-assessment: A threat and risk assessment has been completed on the recipient + security-documentation: Strong security procedures for the collection, transmission, storage and disposal of personal information, and access to it, have been documented + staff-trained: IT & database staff are sufficiently trained in the requirements for protecting personal information + system-backlog: Systems are designed so that access and changes to personal information can be audited by date and user authentication + system-full-control: User accounts, access rights and security authorizations are fully controlled by a system or record management process + breach-protocol: The recipient has an adequate breach notification protocol in place and their staff are trained in its implementation + physically-secure: Computer systems are housed in a physically secure environment + physically-privatearea: There is no public access to areas where computers holding the data will be + data-destruction: The data will be destroyed once its purpose has been accomplished (e.g., the study has been published or other funding agency data retention period expires) + staff-restricted: Access rights are only provided to users on a "need to know" basis consistent with the stated purpose for which the data was collected + +#motives: Motives and Capacity + criminal-value: The disclosed database has potential commercial or criminal value + re-identify-motive: There is a likely motive for the recipient to try to re-identify the disclosed database + re-identify-expertise: The recipient has the technical expertise to attempt to re-identify the disclosed database + re-identify-resources: The recipient has the financial resources to attempt to re-identify the disclosed database + recipient-wants-harm: The recipient may want to harm or embarrass the data custodian + recipient-only-choice: If the recipient does have a possible motive to attempt re-identification, they can not achieve their objectives through other means apart from re-identification + +#privacy: Invasion-of-Privacy + highly-detailed: The personal information is highly detailed + large-database: The database is large / many people would be affected if there was a breach + sensitive-data: The information is of a highly sensitive personal nature + sensitive-context: The information comes from a sensitive context (for example, data about individuals participating in a youth employment program are less sensitive than a similar list containing names and addresses of Hepatitis C and HIV compensation victims) + no-promise: There is no commitment or promise not to disclose to any third party or institution + no-guarantee: The information wasn't compiled or obtained under guarantees that preclude some or all types of disclosure + no-expectation: The information wasn't unsolicited, given freely and voluntarily with little expectation of it being maintained in total confidence + injury-risk: Disclosure of the information carries a probability of causing measurable injury (e.g., identity theft, fraud, etc) + foreign-laws-risk: There is a risk in terms of the possible application of foreign laws + injury-serious: The potential injury to the patients in case of an inappropriate disclosure is grave or serious \ No newline at end of file