/* * Copyright 2011 JBoss Inc * * 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.drools.informer; import org.drools.definition.type.Modifies; import org.drools.definition.type.PropertyReactive; import org.mvel2.templates.util.io.StringBuilderStream; import javax.swing.text.Position; import java.util.*; /** * <p> * An extension of <code>Question</code> which provides a list of possible answers. i.e. a multiple choice question.</code> * </p> * * <p> * <code>presentationStyles</code> could be used to display the possible answers as e.g. radio buttons, or a drop down list. * </p> * * @author Damon Horrell */ @PropertyReactive public class MultipleChoiceQuestion extends Question { private static final long serialVersionUID = 1L; /** * Possible answers are represented internally as comma-delimited value/label pairs i.e. value1=label1,value2=label2,... for efficient XML transport. * * Any commas within the labels are escaped to \, * * Any equals sign within either the values or labels are escaped to \= */ private List<PossibleAnswer> possibleAnswers; private boolean singleAnswer; public MultipleChoiceQuestion() { } public MultipleChoiceQuestion(String type) { super(type); } public MultipleChoiceQuestion(String type, String label) { super(type, label); } protected List<PossibleAnswer> getListOfPossibleAnswers() { // List<PossibleAnswer> result = new ArrayList<PossibleAnswer>(); // String[] split = split(possibleAnswers, ","); // for (int i = 0; i < split.length; i++) { // String s = split[i]; // String[] valueLabel = split(s, "="); // String value = valueLabel[0]; // if (value.equals("null")) { // value = null; // } // String label = valueLabel[1]; // if (label.equals("")) { // label = null; // } // result.add(new PossibleAnswer(value, label)); // } // return result; return possibleAnswers; } public int getNumOfPossibleAnswers() { if (possibleAnswers == null) { return 0; } else { return getListOfPossibleAnswers().size(); } } /** * Gets list of possible answers. * * @return */ public PossibleAnswer[] getPossibleAnswers() { if (possibleAnswers == null) { return null; } // List<PossibleAnswer> result = getListOfPossibleAnswers(); return possibleAnswers.toArray(new PossibleAnswer[] {}); } protected String formatValue(String valueStr) { if (valueStr != null) { if (valueStr.contains(",")) { throw new IllegalArgumentException(); } valueStr = valueStr.replace("=", "\\\\="); } return valueStr; } protected String outcCodeValue(String valueStr) { if (valueStr != null) { valueStr = valueStr.replace(",","\\,"); valueStr = valueStr.replace("=", "\\="); } return valueStr; } /** * Sets list of possible answers. * * @param possibleAnswers */ public void setPossibleAnswers(PossibleAnswer[] possibleAnswers) { if (possibleAnswers == null || possibleAnswers.length == 0) { this.possibleAnswers = null; } else { // StringBuilder sb = new StringBuilder(); // for (int i = 0; i < possibleAnswers.length; i++) { // if (possibleAnswers[i] != null) { // if (sb.length() > 0) { // sb.append(","); // } // String value = formatValue(possibleAnswers[i].value); // sb.append(value); // sb.append('='); // if (possibleAnswers[i].label != null) { // sb.append(possibleAnswers[i].label.replaceAll(",", "\\\\,").replaceAll("=", "\\\\=")); // } // } // } // if (sb.length() > 0) { // this.possibleAnswers = sb.toString(); // } else { // this.possibleAnswers = null; // } this.possibleAnswers = new ArrayList<PossibleAnswer>(); for (int j = 0; j < possibleAnswers.length; j++) { PossibleAnswer pa = possibleAnswers[j]; if (pa != null) { if (pa.getValue() != null && pa.getValue().contains(",")) { throw new IllegalArgumentException("Possible Answers with comma in values are not allowed :" + pa.getValue()); } this.possibleAnswers.add(pa); } } } } /** * Sets list of possible answers. * * This method is provided to support the MVEL syntax in rules e.g. * * <pre> * question.setPossibleAnswers({ * new PossibleAnswer("a", "apple"), * new PossibleAnswer("b", "banana") * }); * </pre> * * @param possibleAnswers */ public void setPossibleAnswers(Object[] possibleAnswers) { if (possibleAnswers == null) { this.possibleAnswers = null; } else { setPossibleAnswers( Arrays.asList(possibleAnswers).toArray(new PossibleAnswer[] {})); } } @Modifies( "possibleAnswers" ) public void setPossibleAnswersByValue(Collection<String> possibleAnswers) { if (possibleAnswers == null) { this.possibleAnswers = null; } else { this.possibleAnswers = new ArrayList<PossibleAnswer>(); for ( String s : possibleAnswers ) { if ( s != null ) { if ( s.contains(",") ) { throw new IllegalArgumentException("Possible Answers with comma in values are not allowed :" + s); } this.possibleAnswers.add( new PossibleAnswer( s, s ) ); } } } } @Modifies( "possibleAnswers" ) public void setPossibleAnswersByValue(String[] possibleAnswers) { if (possibleAnswers == null) { this.possibleAnswers = null; } else { this.possibleAnswers = new ArrayList<PossibleAnswer>(possibleAnswers.length); for (String pas : possibleAnswers) { this.addPossibleAnswer(pas); } } } @Modifies( "possibleAnswers" ) private void addPossibleAnswer(String value) { StringTokenizer st = new StringTokenizer(value,"="); String val = st.nextToken().trim().replace("\"",""); String label = st.hasMoreTokens() ? st.nextToken().trim().replace("\"","") : val; PossibleAnswer pa = new PossibleAnswer(val,label); insertPossibleAnswer(pa,possibleAnswers.size()); } /** * Adds a possible answer. * * This method is provided to support the dynamic alteration of possible answers. * * <b>Do not use for creation of lists</b>. Instead use {@link #setPossibleAnswers(PossibleAnswer[])} * * * @param theValue of the possibleAnswer */ @Modifies( "possibleAnswers" ) public void removePossibleAnswer(String theValue) { PossibleAnswer pos = null; for (PossibleAnswer pa : possibleAnswers) { if ((pa.getValue() != null) && (pa.getValue().equals(theValue))) { pos = pa; break; } } if (pos != null) { if ((getAnswerType() != null) && (getAnswer() != null) && (getAnswer().equals(pos.getValue()))) { setAnswer(null); } possibleAnswers.remove(pos); } } /** * Checks to see if there is a possible answer with the value passed in. * * This method is provided to support the dynamic alteration of possible answers. * Uses String.indexOf internally. * * @param theValue of the possibleAnswer * @return */ public boolean hasPossibleAnswer(String theValue) { PossibleAnswer mock = new PossibleAnswer(theValue,null); return possibleAnswers.contains(mock); } /** * Removes a possible answer. * * This method is provided to support the dynamic alteration of possible answers. * * <b>Do not use for creation of lists</b>. Instead use {@link #setPossibleAnswers(PossibleAnswer[])} * * * @param possibleAnswer * @param atIndex If >= size of array then the answer is added to the end */ @Modifies( "possibleAnswers" ) public void insertPossibleAnswer(PossibleAnswer possibleAnswer, int atIndex) { if (possibleAnswers == null) { // Really should be discouraged from doing this! Least efficient way of building up the list. // PossibleAnswer[] pa = new PossibleAnswer[1]; // pa[0] = possibleAnswer; // setPossibleAnswers(pa); possibleAnswers = new ArrayList<PossibleAnswer>(); possibleAnswers.add(possibleAnswer); return; } if (atIndex < 0) { atIndex = 0; } if (possibleAnswers.size() <= atIndex) { possibleAnswers.add(possibleAnswer); } else { possibleAnswers.add(atIndex, possibleAnswer); } } /** * Gets list of possible answers as a comma delimited string. * * TODO this method can be removed if Guvnor can support array of custom classes. Even just String[] would allow {"a=apple", * "b=banana"} which is slightly better. * * @return * @deprecated */ public String getPossibleAnswersAsString() { if (possibleAnswers == null || possibleAnswers.size() == 0) { return null; } StringBuilder sb = new StringBuilder(); Iterator<PossibleAnswer> iter = possibleAnswers.iterator(); while (iter.hasNext()) { PossibleAnswer pa = iter.next(); sb.append(outcCodeValue(pa.getValue())).append("=").append(outcCodeValue(pa.getLabel())); if (iter.hasNext()) { sb.append(","); } } return sb.toString(); } public String[] getPossibleAnswersValues() { if (possibleAnswers == null || possibleAnswers.size() == 0) { return new String[0]; } String[] ans = new String[ possibleAnswers.size() ]; for ( int j = 0; j < possibleAnswers.size(); j++ ) { ans[j] = possibleAnswers.get( j ).getValue(); } return ans; } public List<String> getPossibleAnswersValuesAsList() { if (possibleAnswers == null || possibleAnswers.size() == 0) { return Collections.emptyList(); } ArrayList<String> ans = new ArrayList<String>( possibleAnswers.size() ); for ( int j = 0; j < possibleAnswers.size(); j++ ) { ans.add ( possibleAnswers.get( j ).getValue() ); } return ans; } /** * Gets list of item ids as a comma delimited string. Implemented for testing purpose only - package visibility * * @return */ String getInternalPossibleAnswersAsString() { return getPossibleAnswersAsString(); } /** * Sets list of possible answers as a comma-delimited string. * * TODO this method can be removed if Guvnor can support array of custom classes. Even just String[] would allow {"a=apple", * "b=banana"} which is slightly better. * * @param possibleAnswers * @deprecated */ @Modifies( "possibleAnswers" ) public void setPossibleAnswersAsString(String possibleAnswers) { if (possibleAnswers == null || possibleAnswers.equals("")) { this.possibleAnswers = null; return; } this.possibleAnswers = new ArrayList<PossibleAnswer>(); possibleAnswers = possibleAnswers.replace("\\,","\\\\\\;"); possibleAnswers = possibleAnswers.replace("\\=","\\\\\\-"); StringTokenizer tok = new StringTokenizer(possibleAnswers,","); while (tok.hasMoreTokens()) { String pa = tok.nextToken().replace("\\\\\\;",",");; StringTokenizer sub = new StringTokenizer(pa,"="); String value = sub.nextToken().replace("\\\\\\-","="); if ("null".equals(value)) { value = null; } String label = sub.hasMoreTokens() ? sub.nextToken().replace("\\\\\\-","=") : ""; PossibleAnswer ans = new PossibleAnswer(value,label); this.possibleAnswers.add(ans); } } public boolean isSingleAnswer() { return singleAnswer; } public void setSingleAnswer(boolean singleAnswer) { this.singleAnswer = singleAnswer; } public String[] getAnswerItems() { return split(String.valueOf(getAnswer()), ","); } /** * For debugging purposes. */ @Override public String toString() { return "Multiple Choice " + super.toString() + " possibleAnswers=" + possibleAnswers; } public static class PossibleAnswer { private String value; private String label; public PossibleAnswer() { } public PossibleAnswer(String value) { this.value = value; this.label = ""; } public PossibleAnswer(String value, String label) { this.value = value; this.label = label; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public String getLabel() { return label; } public void setLabel(String label) { this.label = label; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; PossibleAnswer that = (PossibleAnswer) o; if (value == null && that.value == null) { return label != null && label.equals(that.getLabel()); } if (value != null ? !value.equals(that.value) : that.value != null) return false; return true; } @Override public int hashCode() { return value != null ? value.hashCode() : 0; } /** * @see Object#toString() */ @Override public String toString() { return value + "=" + label; } } }