/* * Copyright (C) 2010 Mark Rijnbeek <mark_rynbeek@users.sf.net> * * Contact: cdk-devel@lists.sourceforge.net * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either version 2.1 * of the License, or (at your option) any later version. * All we ask is that proper credit is given for our work, which includes * - but is not limited to - adding the above copyright notice to the beginning * of your source code files, and to any copyright notice that you may * distribute with programs based on this work. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * */ package org.openscience.cdk.isomorphism.matchers; import java.util.ArrayList; import java.util.List; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.openscience.cdk.exception.CDKException; /** * Represents a list of Rgroup substitutes to be associated with some * {@link RGroupQuery}. * * @cdk.module isomorphism * @cdk.githash * @cdk.keyword Rgroup * @cdk.keyword R group * @cdk.keyword R-group * @author Mark Rijnbeek */ public class RGroupList { /** * Default value for occurrence field. */ public final static String DEFAULT_OCCURRENCE=">0"; /** * Unique number to identify the Rgroup. */ private int rGroupNumber; /** * Indicates that sites labeled with this Rgroup may only be * substituted with a member of the Rgroup or with hydrogen. */ private boolean restH; /** * Occurrence required: * <UL> * <LI>n : exactly n ;</LI> * <LI>n - m : n through m ;</LI> * <LI>> n : greater than n ;</LI> * <LI>< n : fewer than n ;</LI> * <LI>default (blank) is > 0 ;</LI> * </UL> * Any non-contradictory combination of the preceding values is also * allowed; for example "1, 3-7, 9, >11". */ private String occurrence; /** * List of substitute structures. */ private List<RGroup> rGroups; /** * The rGroup (say B) that is required when this one (say A) exists.<p> * This captures the "LOG" information 'IF A (this) THEN B'. */ private int requiredRGroupNumber; /** * Default constructor. */ public RGroupList(int rGroupNumber) { setRGroupNumber(rGroupNumber); this.restH = false; this.occurrence = DEFAULT_OCCURRENCE; this.requiredRGroupNumber=0; } /** * Constructor with attributes given. * * @param rGroupNumber R-Group number * @param restH restH * @param occurrence occurrence * @param requiredRGroupNumber number of other R-Group required * @throws CDKException */ public RGroupList(int rGroupNumber, boolean restH, String occurrence, int requiredRGroupNumber) throws CDKException { setRGroupNumber(rGroupNumber); setRestH(restH); setOccurrence(occurrence); setRequiredRGroupNumber(requiredRGroupNumber); } /** * Setter for rGroupNumber, checks for valid range. * Spec: "value from 1 to 32 *, labels position of Rgroup on root." * @param rGroupNumber R-Group number */ public void setRGroupNumber(int rGroupNumber) { if (rGroupNumber < 1 || rGroupNumber > 32) { throw new RuntimeException("Rgroup number must be between 1 and 32."); } this.rGroupNumber = rGroupNumber; } public int getRGroupNumber() { return rGroupNumber; } public void setRestH(boolean restH) { this.restH = restH; } public boolean isRestH() { return restH; } public void setRequiredRGroupNumber(int rGroupNumberImplicated) { this.requiredRGroupNumber = rGroupNumberImplicated; } public int getRequiredRGroupNumber() { return requiredRGroupNumber; } public void setRGroups(List<RGroup> rGroups) { this.rGroups = rGroups; } public List<RGroup> getRGroups() { return rGroups; } /** * Returns the occurrence value. * @return occurrence */ public String getOccurrence() { return occurrence; } /** * Picky setter for occurrence fields. Validates user input to be conform * the (Symyx) specification. * @param occurrence occurence value */ public void setOccurrence(String occurrence) throws CDKException { if (occurrence == null || occurrence.equals("")) { occurrence = ">0"; //revert to default } else { occurrence = occurrence.trim().replaceAll(" ", ""); if (isValidOccurrenceSyntax(occurrence)) { this.occurrence = occurrence; } else throw new CDKException("Invalid occurence line: " + occurrence); } } /** * Validates the occurrence value. * <UL> * <LI>n : exactly n ;</LI> * <LI>n - m : n through m ;</LI> * <LI>> n : greater than n ;</LI> * <LI>< n : fewer than n ;</LI> * <LI>default (blank) is > 0 ;</LI> * </UL> * Any combination of the preceding values is also * allowed; for example "1, 3-7, 9, >11". * @param occ String to validate. * @return true if valid String provided. */ public static boolean isValidOccurrenceSyntax(String occ) { StringTokenizer st = new StringTokenizer(occ, ","); while (st.hasMoreTokens()) { String cond = st.nextToken().trim().replaceAll(" ", ""); do { //Number: "n" if (match("^\\d+$", cond)) { if (new Integer(cond)<0) // not allowed return false; break; } //Range: "n-m" if (match("^\\d+-\\d+$", cond)) { int from = new Integer(cond.substring(0,cond.indexOf("-"))); int to = new Integer(cond.substring(cond.indexOf("-")+1,cond.length())); if (from<0 || to <0 || to<from) // not allowed return false; break; } //Smaller than: "<n" if (match("^<\\d+$", cond)){ int n = new Integer(cond.substring(cond.indexOf("<")+1,cond.length())); if(n==0) // not allowed return false; break; } //Greater than: ">n" if (match("^>\\d+$", cond)) { break; } return false; } while (1==0); } return true; } /** * Helper method for regular expression matching. * @param regExp regular expression String * @param userInput user's input * @return */ private static boolean match(String regExp, String userInput) { Pattern pattern = Pattern.compile(regExp); Matcher matcher = pattern.matcher(userInput); if (matcher.find()) return true; else return false; } /** * Matches the 'occurrence' condition with a provided maximum number of * RGroup attachments. Returns the valid occurrences (numeric) for these * two combined. If none found, returns empty list.<P> * Example: if R1 occurs 3 times attached to some root structure, then * stating ">5" as an occurrence for that RGoupList does not make * sense: the example R1 can occur 0..3 times. Empty would be returned.<BR> * If the occurence would be >2, then 3 would be returned. Etcetera. * * @param maxAttachments number of attachments * @return valid values by combining a max for R# with the occurrence cond. */ public List<Integer> matchOccurence(int maxAttachments) { List<Integer> validValues = new ArrayList<Integer>(); for (int val = 0; val <= maxAttachments; val++) { boolean addVal=false; StringTokenizer st = new StringTokenizer(occurrence, ","); while (st.hasMoreTokens() && !addVal) { String cond = st.nextToken().trim().replaceAll(" ", ""); if (match("^\\d+$", cond)) { // n if(new Integer(cond)==val) addVal=true; } if (match("^\\d+-\\d+$", cond)) { // n-m int from = new Integer(cond.substring(0,cond.indexOf("-"))); int to = new Integer(cond.substring(cond.indexOf("-")+1,cond.length())); if ( val>=from && val <=to) { addVal=true; } } if (match("^>\\d+$", cond)) { // <n int n = new Integer(cond.substring(cond.indexOf(">")+1,cond.length())); if(val>n){ addVal=true; } } if (match("^<\\d+$", cond)) { // >n int n = new Integer(cond.substring(cond.indexOf("<")+1,cond.length())); if(val<n){ addVal=true; } } if (addVal) { validValues.add(val); } } } return validValues; } public boolean equals(Object obj) { if (obj instanceof RGroupList && this.rGroupNumber == ((RGroupList)obj).rGroupNumber) return true; else return false; } public int hashCode() { return this.rGroupNumber; } }