001/* Replace an instance of a string with another input string according
002 to a regular expression.
003
004 Copyright (c) 2003-2014 The Regents of the University of California.
005 All rights reserved.
006 Permission is hereby granted, without written agreement and without
007 license or royalty fees, to use, copy, modify, and distribute this
008 software and its documentation for any purpose, provided that the above
009 copyright notice and the following two paragraphs appear in all copies
010 of this software.
011
012 IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY
013 FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES
014 ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF
015 THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF
016 SUCH DAMAGE.
017
018 THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES,
019 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
020 MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE
021 PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF
022 CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES,
023 ENHANCEMENTS, OR MODIFICATIONS.
024
025 PT_COPYRIGHT_VERSION_2
026 COPYRIGHTENDKEY
027
028 */
029package ptolemy.actor.lib.string;
030
031import java.util.regex.Matcher;
032import java.util.regex.Pattern;
033import java.util.regex.PatternSyntaxException;
034
035import ptolemy.actor.TypedAtomicActor;
036import ptolemy.actor.TypedIOPort;
037import ptolemy.actor.parameters.PortParameter;
038import ptolemy.data.BooleanToken;
039import ptolemy.data.StringToken;
040import ptolemy.data.expr.Parameter;
041import ptolemy.data.expr.SingletonParameter;
042import ptolemy.data.type.BaseType;
043import ptolemy.kernel.CompositeEntity;
044import ptolemy.kernel.util.Attribute;
045import ptolemy.kernel.util.IllegalActionException;
046import ptolemy.kernel.util.NameDuplicationException;
047import ptolemy.kernel.util.Workspace;
048
049///////////////////////////////////////////////////////////////////
050//// StringReplace
051
052/**
053 On each firing, look for instances of the pattern specified by <i>pattern</i>
054 in <i>stringToEdit</i> and replace them with the string given by
055 <i>replacement</i>.  If <i>replaceAll</i> is true, then replace
056 all instances that match <i>pattern</i>.  Otherwise, replace only
057 the first instance that matches.  If there is no match, then the
058 output is the string provided by <i>stringToEdit</i>, unchanged.
059 The <i>pattern</i> is given by a regular expression.
060 For a reference on regular expression syntax see:
061 <a href="http://download.oracle.com/javase/tutorial/essential/regex/#in_browser">
062 http://download.oracle.com/javase/tutorial/essential/regex/</a>.
063
064 <p>
065 The <i>replacement</i> string, as usual with string-valued parameters
066 in Ptolemy II, can include references to parameter values in scope.
067 E.g., if the enclosing composite actor has a parameter named "x"
068 with value 1, say, then the replacement string a${x}b will become
069 "a1b".
070 <p>
071 In addition, the <i>replacement</i> string can reference the pattern
072 that is matched using the syntax "$$0".  For example, the regular
073 expression "t[a-z]+" in <i>pattern</i> will match the character t followed by a
074 sequence of one or more lower-case letters.
075 If <i>replacement</i> is "p$$0" then "this is a test" becomes
076 "pthis is a ptest".
077 <p>
078 If the <i>pattern</i> contains parenthesized subpatterns, such
079 as "(t[a-z]+)|(T([a-z]+))", then the value of <i>replacement</i>
080 can reference the match of each parenthesized subpattern with
081 the syntax "$$n", where "n" is an integer between 1 and 9.
082 For example, if <i>pattern</i>="(t[a-z]+)|(T([a-z]+))"
083 and <i>replacement</i>="p$$1$$3", then "this is a Test" becomes
084 "pthis is a pest". The index "n" corresponds to the order
085 of opening parentheses in the pattern.
086 <p>
087 To get a "$" into the replacement string, use
088 "\$$".  To get a "\" into the replacement string, use "\\".
089
090 @author Antonio Yordan-Nones, Neil E. Turner, Edward A. Lee
091 @version $Id$
092 @since Ptolemy II 4.0
093 @Pt.ProposedRating Green (djstone)
094 @Pt.AcceptedRating Green (net)
095 */
096public class StringReplace extends TypedAtomicActor {
097    /** Construct an actor with the given container and name.
098     *  @param container The container.
099     *  @param name The name of this actor.
100     *  @exception IllegalActionException If the actor cannot be contained
101     *   by the proposed container.
102     *  @exception NameDuplicationException If the container already has an
103     *   actor with this name.
104     */
105    public StringReplace(CompositeEntity container, String name)
106            throws NameDuplicationException, IllegalActionException {
107        super(container, name);
108
109        // Create new parameters and ports.
110        // Set default values of the parameters and type constraints.
111        pattern = new PortParameter(this, "pattern");
112        pattern.setStringMode(true);
113        pattern.setExpression("");
114        new SingletonParameter(pattern.getPort(), "_showName")
115                .setToken(BooleanToken.TRUE);
116
117        replacement = new PortParameter(this, "replacement");
118        replacement.setStringMode(true);
119        replacement.setExpression("");
120        new SingletonParameter(replacement.getPort(), "_showName")
121                .setToken(BooleanToken.TRUE);
122
123        stringToEdit = new PortParameter(this, "stringToEdit");
124        stringToEdit.setStringMode(true);
125        stringToEdit.setExpression("");
126        new SingletonParameter(stringToEdit.getPort(), "_showName")
127                .setToken(BooleanToken.TRUE);
128
129        output = new TypedIOPort(this, "output", false, true);
130        output.setTypeEquals(BaseType.STRING);
131
132        replaceAll = new Parameter(this, "replaceAll");
133        replaceAll.setExpression("true");
134        replaceAll.setTypeEquals(BaseType.BOOLEAN);
135
136        regularExpression = new Parameter(this, "regularExpression");
137        regularExpression.setExpression("true");
138        regularExpression.setTypeEquals(BaseType.BOOLEAN);
139    }
140
141    ///////////////////////////////////////////////////////////////////
142    ////                     ports and parameters                  ////
143
144    /** The string to edit by replacing substrings that match the
145     *  specified pattern with the specified replacement. This is
146     *  a string that defaults to the empty string.
147     */
148    public PortParameter stringToEdit;
149
150    /** The output port on which the edited string is produced.
151     *  This has type string.
152     */
153    public TypedIOPort output;
154
155    /** The pattern used to pattern match and replace the stringToEdit
156     *  string. It is an empty string by default.
157     */
158    public PortParameter pattern;
159
160    /** The replacement string that replaces any matched instance of the
161     *  pattern. It is an empty string by default.
162     */
163    public PortParameter replacement;
164
165    /** When the boolean value is true, replace all instances that match the
166     *  pattern, and when false, replace the first instance.
167     */
168    public Parameter replaceAll;
169
170    /** If true, interpret the pattern as a regular expression. Otherwise,
171     *  interpret it as the literal string to replace. This is a boolean
172     *  that defaults to true.
173     */
174    public Parameter regularExpression;
175
176    ///////////////////////////////////////////////////////////////////
177    ////                         public methods                    ////
178
179    /** Override the base class to compile a regular expression when
180     *  it is changed.
181     *  @param attribute The attribute that changed.
182     *  @exception IllegalActionException If the specified attribute
183     *   is <i>pattern</i> and the regular expression fails to
184     *   compile.
185     */
186    @Override
187    public void attributeChanged(Attribute attribute)
188            throws IllegalActionException {
189        if (attribute == pattern) {
190            _patternValue = ((StringToken) pattern.getToken()).stringValue();
191            // FIXME: What is the following about???
192            if (_patternValue.equals("\\r")) {
193                _patternValue = "\r";
194            }
195            _pattern = null;
196        } else {
197            super.attributeChanged(attribute);
198        }
199    }
200
201    /** Clone the attribute into the specified workspace.  The resulting
202     *  object has no base directory name nor any reference to any open stream.
203     *  @param workspace The workspace for the new object.
204     *  @return A new attribute.
205     *  @exception CloneNotSupportedException If a derived class contains
206     *   an attribute that cannot be cloned.
207     */
208    @Override
209    public Object clone(Workspace workspace) throws CloneNotSupportedException {
210        StringReplace newObject = (StringReplace) super.clone(workspace);
211        try {
212            newObject.attributeChanged(newObject.pattern);
213        } catch (IllegalActionException e) {
214            // Should not occur.
215            throw new CloneNotSupportedException("Cloning failed");
216        }
217        return newObject;
218    }
219
220    /** Perform pattern matching and substring replacement, and output
221     *  the modified string. If no match is found, output the
222     *  unmodified stringToEdit string.
223     *  @exception IllegalActionException If there is no director.
224     */
225    @Override
226    public void fire() throws IllegalActionException {
227        super.fire();
228
229        replacement.update();
230        stringToEdit.update();
231        pattern.update();
232
233        String replacementValue = ((StringToken) replacement.getToken())
234                .stringValue();
235        String stringToEditValue = ((StringToken) stringToEdit.getToken())
236                .stringValue();
237        boolean replaceAllTokens = ((BooleanToken) replaceAll.getToken())
238                .booleanValue();
239        boolean regularExpressionValue = ((BooleanToken) regularExpression
240                .getToken()).booleanValue();
241
242        if (regularExpressionValue) {
243            if (_pattern == null) {
244                try {
245                    String patternValue = ((StringToken) pattern.getToken())
246                            .stringValue();
247                    _pattern = Pattern.compile(patternValue);
248                } catch (PatternSyntaxException ex) {
249                    String patternValue = ((StringToken) pattern.getToken())
250                            .stringValue();
251                    throw new IllegalActionException(this, ex,
252                            "Failed to compile regular expression \""
253                                    + patternValue + "\"");
254                }
255            }
256            Matcher match = _pattern.matcher(stringToEditValue);
257            String outputString = "";
258
259            // Unfortunately, the String class throws runtime exceptions
260            // if something goes wrong, so we have to catch them.
261            try {
262                if (replaceAllTokens) {
263                    outputString = match.replaceAll(replacementValue);
264                } else {
265                    outputString = match.replaceFirst(replacementValue);
266                }
267            } catch (Exception ex) {
268                throw new IllegalActionException(this, ex,
269                        "String replace failed.");
270            }
271
272            output.send(0, new StringToken(outputString));
273        } else {
274            // No regular expression.
275            String outputString;
276            if (replaceAllTokens) {
277                outputString = stringToEditValue.replaceAll(_patternValue,
278                        replacementValue);
279            } else {
280                outputString = stringToEditValue.replace(_patternValue,
281                        replacementValue);
282            }
283            output.send(0, new StringToken(outputString));
284        }
285    }
286
287    ///////////////////////////////////////////////////////////////////
288    ////                         private variables                 ////
289
290    // The compiled regular expression.
291    private Pattern _pattern;
292
293    // The replacement string.
294    private String _patternValue;
295}