package net.sourceforge.pmd.eclipse.ui.preferences.panelmanagers; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Set; import net.sourceforge.pmd.Rule; import net.sourceforge.pmd.RulePriority; import net.sourceforge.pmd.RuleSet; import net.sourceforge.pmd.eclipse.plugin.PMDPlugin; import net.sourceforge.pmd.eclipse.plugin.UISettings; import net.sourceforge.pmd.eclipse.runtime.builder.MarkerUtil; import net.sourceforge.pmd.eclipse.ui.LabelProvider; import net.sourceforge.pmd.eclipse.ui.ShapePicker; import net.sourceforge.pmd.eclipse.ui.nls.StringKeys; import net.sourceforge.pmd.eclipse.ui.preferences.br.ImplementationType; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleFieldAccessor; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleSelection; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleUtil; import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleVisitor; import net.sourceforge.pmd.eclipse.ui.preferences.br.ValueChangeListener; import net.sourceforge.pmd.eclipse.ui.preferences.editors.SWTUtil; import net.sourceforge.pmd.eclipse.ui.preferences.editors.TypeText; import net.sourceforge.pmd.lang.Language; import net.sourceforge.pmd.lang.LanguageRegistry; import net.sourceforge.pmd.lang.LanguageVersion; import net.sourceforge.pmd.lang.rule.XPathRule; import net.sourceforge.pmd.util.StringUtil; 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.Combo; import org.eclipse.swt.widgets.Composite; import org.eclipse.swt.widgets.Control; import org.eclipse.swt.widgets.Event; import org.eclipse.swt.widgets.Label; import org.eclipse.swt.widgets.Listener; import org.eclipse.swt.widgets.Text; /** * * @author Brian Remedios */ public class RulePanelManager extends AbstractRulePanelManager { private RuleTarget target; private Text nameField; private TypeText implementationClassField; private Combo ruleSetNameField; private Button ruleReferenceButton; private Combo languageCombo; private Combo priorityCombo; private ShapePicker priorityDisplay; private Label minLanguageLabel; private Label maxLanguageLabel; private Combo minLanguageVersionCombo; private Combo maxLanguageVersionCombo; private Combo implementationTypeCombo; private Button usesTypeResolutionButton; private Button usesDfaButton; private List<Label> labels; private boolean inSetup; private Set<String> currentRuleNames; public static final String ID = "rule"; // TODO move to RuleSet class public static final Comparator<RuleSet> byNameComparator = new Comparator<RuleSet>() { public int compare(RuleSet rsA, RuleSet rsB) { return rsA.getName().compareTo(rsB.getName()); }; }; public RulePanelManager(String theTitle, EditorUsageMode theMode, ValueChangeListener theListener, RuleTarget theRuleSource) { this(ID, theTitle, theMode, theListener, theRuleSource); } public RulePanelManager(String theId, String theTitle, EditorUsageMode theMode, ValueChangeListener theListener, RuleTarget theRuleSource) { super(theId, theTitle, theMode, theListener); target = theRuleSource; } public void showControls(boolean flag) { nameField.setVisible(flag); implementationTypeCombo.setVisible(flag); implementationClassField.setVisible(flag); ruleSetNameField.setVisible(flag); languageCombo.setVisible(flag); priorityCombo.setVisible(flag); priorityDisplay.setVisible(flag); minLanguageVersionCombo.setVisible(flag); maxLanguageVersionCombo.setVisible(flag); usesDfaButton.setVisible(flag); usesTypeResolutionButton.setVisible(flag); for (Label label : labels) label.setVisible(flag); } @Override protected void clearControls() { nameField.setText(""); ruleSetNameField.select(-1); implementationClassField.setType(null); ruleSetNameField.setText(""); languageCombo.select(-1); priorityCombo.select(-1); priorityDisplay.setItems(null); usesDfaButton.setSelection(false); usesTypeResolutionButton.setSelection(false); clearLanguageVersionCombos(); } private void clearLanguageVersionCombos() { SWTUtil.deselectAll(minLanguageVersionCombo); SWTUtil.deselectAll(maxLanguageVersionCombo); } private void showLanguageVersionFields(Language language) { int versionCount = language == null ? 0 : language.getVersions().size(); boolean hasVersions = versionCount > 1; minLanguageLabel.setVisible(hasVersions); maxLanguageLabel.setVisible(hasVersions); minLanguageVersionCombo.setVisible(hasVersions); maxLanguageVersionCombo.setVisible(hasVersions); if (hasVersions) { List<LanguageVersion> versions = new ArrayList<LanguageVersion>(); versions.add(null); // allow no selection versions.addAll(language.getVersions()); populate(minLanguageVersionCombo, versions); populate(maxLanguageVersionCombo, versions); } } private void populate(Combo field, List<LanguageVersion> versions) { field.removeAll(); for (LanguageVersion version : versions) { field.add(version == null ? "" : version.getName()); } } private Set<Comparable<?>> uniquePriorities() { if (rules == null) return Collections.emptySet(); return RuleUtil.uniqueAspects(rules, RuleFieldAccessor.priority); } private String commonLanguageMinVersionName() { if (rules == null) return null; LanguageVersion version = RuleUtil.commonLanguageMinVersion(rules); return version == null ? null : version.getName(); } private String commonLanguageMaxVersionName() { if (rules == null) return null; LanguageVersion version = (LanguageVersion)RuleUtil.commonAspect(rules, RuleFieldAccessor.maxLanguageVersion); return version == null ? null : version.getName(); } private String commonPriorityName() { if (rules == null) return null; RulePriority priority = RuleUtil.commonPriority(rules); return priority == null ? null : UISettings.labelFor(priority); } private boolean allRulesUseTypeResolution() { return rules != null && RuleUtil.allUseTypeResolution(rules); } private boolean allRulesUseDfa() { return rules != null && RuleUtil.allUseDfa(rules); } @Override protected void adapt() { show(ruleSetNameField, RuleUtil.commonRuleset(rules)); Language language = RuleUtil.commonLanguage(rules); show(languageCombo, language == null ? "" : language.getName()); ImplementationType impType = rules == null ? ImplementationType.Mixed : rules.implementationType(); implementationType(impType); implementationTypeCombo.setEnabled(creatingNewRule()); Class<?> impClass = RuleUtil.commonImplementationClass(rules); show(implementationClassField, impClass); implementationClassField.setEnabled( impClass != null); show(priorityCombo, commonPriorityName()); priorityDisplay.setItems(uniquePriorities().toArray()); show(usesTypeResolutionButton, allRulesUseTypeResolution()); show(usesDfaButton, allRulesUseDfa()); showLanguageVersionFields(language); show(minLanguageVersionCombo, commonLanguageMinVersionName()); show(maxLanguageVersionCombo, commonLanguageMaxVersionName()); Rule soleRule = soleRule(); if (soleRule == null) { shutdown(nameField); } else { show(nameField, asCleanString(soleRule.getName())); } validate(); } @Override protected boolean canManageMultipleRules() { return true; } @Override public Control setupOn(Composite parent) { inSetup = true; labels = new ArrayList<Label>(); Composite dlgArea = new Composite(parent, SWT.NONE); GridLayout gridLayout = new GridLayout(); gridLayout.numColumns = 6; dlgArea.setLayout(gridLayout); // put first if we're not creating a new rule if (!creatingNewRule()) buildPriorityControls(dlgArea); Label nameLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_NAME); GridData data = new GridData(); data.horizontalSpan = 1; nameLabel.setLayoutData(data); nameField = buildNameText(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.FILL; data.horizontalSpan = 5; data.grabExcessHorizontalSpace = true; nameField.setLayoutData(data); Label ruleSetNameLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_RULESET_NAME); data = new GridData(); data.horizontalSpan = 1; ruleSetNameLabel.setLayoutData(data); ruleSetNameField = buildRuleSetNameField(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.FILL; data.horizontalSpan = 5; data.grabExcessHorizontalSpace = true; ruleSetNameField.setLayoutData(data); Label implTypeLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_IMPLEMENTED_BY); data = new GridData(); data.horizontalSpan = 1; implTypeLabel.setLayoutData(data); implementationTypeCombo = buildImplementationTypeCombo(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.FILL; data.horizontalSpan = 5; data.grabExcessHorizontalSpace = true; implementationTypeCombo.setLayoutData(data); Label implementationClassLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_IMPLEMENTATION_CLASS); data = new GridData(); data.horizontalSpan = 1; implementationClassLabel.setLayoutData(data); implementationClassField = buildImplementationClassField(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.FILL; data.horizontalSpan = 5; data.grabExcessHorizontalSpace = true; implementationClassField.setLayoutData(data); buildLabel(dlgArea, null); usesTypeResolutionButton = buildUsesTypeResolutionButton(dlgArea); usesDfaButton = buildUsesDfaButton(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.FILL; data.horizontalSpan = 4; data.grabExcessHorizontalSpace = true; usesDfaButton.setLayoutData(data); // buildLabel(dlgArea, null); Label languageLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_LANGUAGE); data = new GridData(); data.horizontalSpan = 1; languageLabel.setLayoutData(data); languageCombo = buildLanguageCombo(dlgArea); data = new GridData(); data.horizontalAlignment = GridData.BEGINNING; data.horizontalSpan = 1; data.grabExcessHorizontalSpace = false; languageCombo.setLayoutData(data); GridData lblGD = new GridData(); lblGD.horizontalSpan = 1; lblGD.horizontalAlignment = SWT.END; GridData cmboGD = new GridData(); cmboGD.horizontalAlignment = GridData.FILL; cmboGD.horizontalSpan = 1; cmboGD.grabExcessHorizontalSpace = true; minLanguageLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_LANGUAGE_MIN); minLanguageLabel.setAlignment(SWT.RIGHT); minLanguageLabel.setLayoutData(lblGD); minLanguageVersionCombo = buildLanguageVersionCombo(dlgArea, true); minLanguageVersionCombo.setLayoutData(cmboGD); maxLanguageLabel = buildLabel(dlgArea, StringKeys.PREF_RULEEDIT_LABEL_LANGUAGE_MAX); maxLanguageLabel.setAlignment(SWT.RIGHT); maxLanguageLabel.setLayoutData(lblGD); maxLanguageVersionCombo = buildLanguageVersionCombo(dlgArea, false); maxLanguageVersionCombo.setLayoutData(cmboGD); if (creatingNewRule()) { buildPriorityControls(dlgArea); // put it at the bottom when creating new rules implementationType(ImplementationType.XPath); } setControl(dlgArea); validate(); inSetup = false; return dlgArea; } /** * Build the rule reference button */ private Button buildRuleReferenceButton(Composite parent) { final Button button = new Button(parent, SWT.CHECK); button.setText(SWTUtil.stringFor(StringKeys.PREF_RULEEDIT_BUTTON_RULE_REFERENCE)); button.setEnabled(false); // button.setSelection(rule() instanceof RuleReference); return button; } private Label buildLabel(Composite parent, String msgKey) { Label label = new Label(parent, SWT.NONE); label.setText(msgKey == null ? "" : SWTUtil.stringFor(msgKey)); labels.add(label); return label; } private Text buildNameText(Composite parent) { int style = creatingNewRule() ? SWT.SINGLE | SWT.BORDER : SWT.READ_ONLY | SWT.BORDER; final Text nameField = new Text(parent, style); nameField.setFocus(); Listener validateListener = new Listener() { public void handleEvent(Event event) { validateRuleParams(); } }; nameField.addListener(SWT.Modify, validateListener); nameField.addListener(SWT.DefaultSelection, validateListener); return nameField; } private Combo buildRuleSetNameField(Composite parent) { int style = creatingNewRule() ? SWT.BORDER : SWT.READ_ONLY; Combo field = new Combo(parent, style); Set<RuleSet> rs = PMDPlugin.getDefault().getRuleSetManager().getRegisteredRuleSets(); RuleSet[] ruleSets = rs.toArray(new RuleSet[rs.size()]); Arrays.sort(ruleSets, byNameComparator); for (RuleSet ruleSet : ruleSets) { field.add(ruleSet.getName().trim()); } Listener validateListener = new Listener() { public void handleEvent(Event event) { validateRuleParams(); } }; field.addListener(SWT.Modify, validateListener); field.addListener(SWT.DefaultSelection, validateListener); return field; } private void implementationType(ImplementationType type) { switch (type) { case XPath: { implementationClassField.setEnabled(false); usesTypeResolutionButton.setEnabled(false); usesTypeResolutionButton.setSelection(true); usesDfaButton.setEnabled(false); usesDfaButton.setSelection(false); implementationTypeCombo.select(0); if (creatingNewRule()) { implementationClassField.setType(XPathRule.class); } break; } case Java: { implementationClassField.setEnabled(true); usesTypeResolutionButton.setEnabled(true); usesTypeResolutionButton.setSelection(true); usesDfaButton.setEnabled(true); usesDfaButton.setSelection(false); implementationTypeCombo.select(1); if (creatingNewRule()) { implementationClassField.setType(null); } break; } case Mixed: { implementationTypeCombo.deselectAll(); } } validateRuleParams(); } private Combo buildImplementationTypeCombo(Composite parent) { final Combo combo = new Combo(parent, SWT.READ_ONLY); combo.add("XPath script"); combo.add("Java class"); combo.addSelectionListener(new SelectionAdapter() { @Override public void widgetSelected(SelectionEvent event) { int idx = combo.getSelectionIndex(); switch (idx) { case 0: { implementationType(ImplementationType.XPath); break; } case 1: { implementationType(ImplementationType.Java); break; } case -1: { implementationType(ImplementationType.Mixed); break; } } } }); combo.select(0); return combo; } private Combo buildLanguageCombo(Composite parent) { final List<Language> languages = LanguageRegistry.findWithRuleSupport(); final Combo combo = new Combo(parent, SWT.READ_ONLY); Language deflt = LanguageRegistry.getDefaultLanguage(); int selectionIndex = -1; for (int i = 0; i < languages.size(); i++) { if (languages.get(i) == deflt) selectionIndex = i; combo.add(languages.get(i).getName()); } combo.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { if (rules == null) return; Language language = languages.get(combo.getSelectionIndex()); rules.setLanguage(language); updateLanguageVersionComboSelections(language); changed(null, language.getName()); } }); combo.select(selectionIndex); return combo; } private void updateLanguageVersionComboSelections(Language language) { List<LanguageVersion> versions = language.getVersions(); if (versions.size() > 1) { showLanguageVersionFields(language); show(minLanguageVersionCombo, commonLanguageMinVersionName()); show(maxLanguageVersionCombo, commonLanguageMaxVersionName()); } else { showLanguageVersionFields(null); } } private Language selectedLanguage() { int index = languageCombo.getSelectionIndex(); if (index < 0) return null; // should never happen! return LanguageRegistry.findWithRuleSupport().get(index); } private LanguageVersion selectedVersionIn(Combo versionCombo) { int index = versionCombo.getSelectionIndex(); if (index < 0) return null; return selectedLanguage().getVersions().get(index); } private Combo buildLanguageVersionCombo(Composite parent, final boolean isMinVersion) { int style = creatingNewRule() ? SWT.SINGLE | SWT.BORDER : SWT.READ_ONLY | SWT.BORDER; final Combo combo = new Combo(parent, style); combo.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { if (rules == null) return; final int selIdx = combo.getSelectionIndex(); final LanguageVersion version = selIdx == 0 ? null : selectedLanguage().getVersions().get(selIdx-1); RuleVisitor visitor = new RuleVisitor() { public boolean accept(Rule rule) { if (isMinVersion) { rule.setMinimumLanguageVersion(version); } else { rule.setMaximumLanguageVersion(version); } return true; } }; rules.rulesDo(visitor); valueChanged(null, version == null ? "" : version.getName()); } }); return combo; } private Combo buildPriorityCombo(Composite parent) { final Combo combo = new Combo(parent, SWT.READ_ONLY | SWT.BORDER); // combo.setEditable(false); final RulePriority[] priorities = RulePriority.values(); for (RulePriority rulePriority : priorities) { combo.add(UISettings.labelFor(rulePriority)); } if (rules != null) { RulePriority priority = RuleUtil.commonPriority(rules); int index = priority == null ? -1 : priority.getPriority() - 1; combo.select(index); } combo.addSelectionListener(new SelectionAdapter() { public void widgetSelected(SelectionEvent event) { setPriority(priorities[ combo.getSelectionIndex() ]); validateRuleParams(); } }); combo.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, false, false, 1, 1)); return combo; } private void setPriority(RulePriority priority) { priorityDisplay.setItems(new Object[] {priority}); if (rules != null) rules.setPriority(priority); valueChanged(null, priority); } private Button buildUsesTypeResolutionButton(Composite parent) { final Button button = new Button(parent, SWT.CHECK); button.setText(SWTUtil.stringFor(StringKeys.PREF_RULEEDIT_BUTTON_USES_TYPE_RESOLUTION)); return button; } private Button buildUsesDfaButton(Composite parent) { final Button button = new Button(parent, SWT.CHECK); button.setText(SWTUtil.stringFor(StringKeys.PREF_RULEEDIT_BUTTON_USES_DFA)); return button; } private boolean hasValidRuleType() { if (!implementationClassField.isEnabled()) return true; Class<?> newType = implementationClassField.getType(false); return newType != null && Rule.class.isAssignableFrom(newType); } private String nameFieldValue() { return nameField.getText().trim(); } private void buildPriorityControls(Composite parent) { Label priorityLabel = buildLabel(parent, StringKeys.PREF_RULEEDIT_LABEL_PRIORITY); GridData data = new GridData(); data.horizontalSpan = 1; priorityLabel.setLayoutData(data); priorityCombo = buildPriorityCombo(parent); priorityDisplay = new ShapePicker(parent, SWT.NONE, 14); priorityDisplay.setLayoutData( new GridData(SWT.LEFT, SWT.CENTER, true, false, 4, 1)); priorityDisplay.setShapeMap(UISettings.shapesByPriority()); priorityDisplay.tooltipProvider( new LabelProvider() { public String labelFor(Object item) { return UISettings.labelFor((RulePriority)item); } } ); priorityDisplay.setSize(120, 25); } private boolean hasValidRuleName() { if (creatingNewRule() && !isValidRuleName(nameFieldValue())) return false; if (rules == null || rules.hasMultipleRules()) return true; return isValidRuleName(nameFieldValue()); } private boolean hasExistingRuleName() { if (currentRuleNames == null) currentRuleNames = MarkerUtil.currentRuleNames(); return currentRuleNames.contains(nameFieldValue()); } private boolean hasValidRulesetName() { String name = ruleSetNameField.getText(); return isValidRulesetName(name); } private static boolean hasNoSelection(Combo combo) { return combo.getSelectionIndex() < 0; } private boolean hasValidChoice(Combo combo) { if (creatingNewRule() && hasNoSelection(combo)) return false; if (rules == null || rules.hasMultipleRules()) return true; return priorityCombo.getSelectionIndex() >= 0; } protected List<String> fieldErrors() { List<String> errors = new ArrayList<String>(); if (!hasValidRuleType()) errors.add("Invalid rule class"); if (!hasValidRuleName()) errors.add("Invalid rule name"); if (creatingNewRule() && hasExistingRuleName()) errors.add("Rule name is already in use"); if (!hasValidRulesetName()) errors.add("Invalid ruleset name"); if (!hasValidChoice(priorityCombo)) errors.add("No priority selected"); if (!hasValidChoice(languageCombo)) errors.add("No language selected"); return errors; } private void validateRuleParams() { boolean isOk = validate(); if (isOk && creatingNewRule()) { populateRuleInstance(); } if (inSetup) return; if (target != null) { target.rule( isOk ? rules.soleRule() : null ); } } private void copyLocalValuesTo(Rule rule) { rule.setName(nameFieldValue()); rule.setRuleSetName(ruleSetNameField.getText()); Language language = selectedLanguage(); rule.setLanguage(language); rule.setPriority( RulePriority.valueOf(priorityCombo.getSelectionIndex()+1) ); if (usesTypeResolutionButton.getSelection()) { rule.setUsesTypeResolution(); } if (usesDfaButton.getSelection()) { rule.setUsesDFA(); } rule.setMinimumLanguageVersion(selectedVersionIn(minLanguageVersionCombo)); rule.setMaximumLanguageVersion(selectedVersionIn(maxLanguageVersionCombo)); } private void populateRuleInstance() { Class<Rule> ruleType = (Class<Rule>)implementationClassField.getType(true); try { Rule newRule = ruleType.newInstance(); if (rules == null) { rules = new RuleSelection(newRule); } else { if (newRule.getClass() != soleRule().getClass()) { rules.soleRule(newRule); } } copyLocalValuesTo(rules.soleRule()); } catch (InstantiationException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } } private TypeText buildImplementationClassField(Composite parent) { int style = creatingNewRule() ? SWT.SINGLE | SWT.BORDER : SWT.READ_ONLY | SWT.BORDER; final TypeText classField = new TypeText(parent, style, true, ""); classField.setEnabled(false); Listener validateListener = new Listener() { public void handleEvent(Event event) { validateRuleParams(); } }; classField.addListener(SWT.FocusOut, validateListener); classField.addListener(SWT.DefaultSelection, validateListener); return classField; } private static boolean isValidRuleName(String candidateName) { if (StringUtil.isEmpty(candidateName)) return false; // TODO return true; } private static boolean isValidRulesetName(String candidateName) { if (StringUtil.isEmpty(candidateName)) return false; // TODO return true; } }