Changes merged from release35 branch.
The module is going to be updated at http://vcsgeneric.netbeans.org/profiles/index.html
1.1 --- a/vcs.profiles.clearcase/build.xml Fri Jun 13 06:20:12 2003 +0000
1.2 +++ b/vcs.profiles.clearcase/build.xml Fri Jun 13 09:13:36 2003 +0000
1.3 @@ -41,9 +41,6 @@
1.4 <pathelement location="${nbroot1}/openidex/netbeans/modules/autoload/openidex.jar"/>
1.5 <pathelement location="${nbroot1}/vcscore/netbeans/modules/autoload/vcscore.jar"/>
1.6 <pathelement location="${nbroot1}/vcsgeneric/netbeans/modules/vcsgen.jar"/>
1.7 - <fileset dir="${nbroot1}/libs/external">
1.8 - <include name="regexp*.jar"/>
1.9 - </fileset>
1.10 </classpath>
1.11 </javac>
1.12 </target>
2.1 --- a/vcs.profiles.clearcase/manifest.mf Fri Jun 13 06:20:12 2003 +0000
2.2 +++ b/vcs.profiles.clearcase/manifest.mf Fri Jun 13 09:13:36 2003 +0000
2.3 @@ -3,8 +3,7 @@
2.4 OpenIDE-Module-Localizing-Bundle: org/netbeans/modules/vcs/profiles/clearcase/Bundle.properties
2.5 OpenIDE-Module-Specification-Version: 1.2
2.6 OpenIDE-Module-Implementation-Version: @BUILD_NUMBER_SUBST@
2.7 -OpenIDE-Module-IDE-Dependencies: IDE/1 > 1.24
2.8 +OpenIDE-Module-IDE-Dependencies: IDE/1 > 3.18
2.9 OpenIDE-Module-Module-Dependencies: org.netbeans.modules.vcscore/1 > 1.6, org.netbeans.modules.vcs.advanced/1 > 1.6
2.10 OpenIDE-Module-Layer: org/netbeans/modules/vcs/profiles/clearcase/mf-layer.xml
2.11 -OpenIDE-Module-Package-Dependencies: org.apache.regexp[RE]
2.12
3.1 --- a/vcs.profiles.clearcase/src/org/netbeans/modules/vcs/profiles/clearcase/config/Bundle.properties Fri Jun 13 06:20:12 2003 +0000
3.2 +++ b/vcs.profiles.clearcase/src/org/netbeans/modules/vcs/profiles/clearcase/config/Bundle.properties Fri Jun 13 09:13:36 2003 +0000
3.3 @@ -2,12 +2,28 @@
3.4
3.5 ClearCase_ROOT=ClearCase
3.6
3.7 +LIST_Mnemonic=R
3.8 +
3.9 +REASON=Reason
3.10 +
3.11 +REASON_MNEMONIC=R
3.12 +
3.13 +DIRNAME=Directory Name
3.14 +
3.15 +DIRNAME_MNEMONIC=N
3.16 +
3.17 CMD_Refresh=Refresh
3.18
3.19 +CMD_Update=Update Manager
3.20 +
3.21 CMD_Mkdir=Create Directory
3.22
3.23 CMD_Remove=Remove Name from Directory
3.24
3.25 +CMD_RemoveDirectory=Remove Directory Name from Directory
3.26 +
3.27 +CMD_Findmerge_Graphical=Start a Merge
3.28 +
3.29 CMD_Refresh_Recursively=Refresh Recursively
3.30
3.31 CMD_Check_In=Check In
3.32 @@ -22,7 +38,9 @@
3.33
3.34 CMD_Add_To_Source_Control=Add To Source Control
3.35
3.36 -CMD_Find_Checkouts=Find Checkouts
3.37 +CMD_Find_Checkouts_Recursively=Find Checked Out Recursively
3.38 +
3.39 +CMD_Find_Checkouts_All=Find All Checked Out in View
3.40
3.41 CMD_History=History (Text)
3.42
4.1 --- a/vcs.profiles.clearcase/src/org/netbeans/modules/vcs/profiles/clearcase/list/ClearCaseListCommand.java Fri Jun 13 06:20:12 2003 +0000
4.2 +++ b/vcs.profiles.clearcase/src/org/netbeans/modules/vcs/profiles/clearcase/list/ClearCaseListCommand.java Fri Jun 13 09:13:36 2003 +0000
4.3 @@ -25,22 +25,25 @@
4.4
4.5 /**
4.6 * Implements List command for ClearCase.
4.7 - * @author Alan Tai, Martin Entlicher
4.8 + * @author Alan Tai, Martin Entlicher, Peter Wisnovsky
4.9 */
4.10 public class ClearCaseListCommand extends AbstractListCommand
4.11 {
4.12 -
4.13 - private Debug E=new Debug("ClearCaseListCommand",true);
4.14 - private Debug D=E;
4.15 -
4.16 - private String dir=null;
4.17 + /** Directory in which we are executing */
4.18 + private String dir = null;
4.19
4.20 /** Creates new ClearCaseListCommand */
4.21 public ClearCaseListCommand()
4.22 {
4.23 }
4.24
4.25 - public void setFileSystem(VcsFileSystem fileSystem) {
4.26 + /**
4.27 + * This is odd, since it only calls super.setFileSystem. Yet
4.28 + * without it it dumps core. Perhaps someone is doing reflection
4.29 + * on the class?
4.30 + */
4.31 + public void setFileSystem(VcsFileSystem fileSystem)
4.32 + {
4.33 super.setFileSystem(fileSystem);
4.34 }
4.35
4.36 @@ -57,12 +60,14 @@
4.37 this.dir = ""; // NOI18N
4.38 }
4.39 String module = (String) vars.get("MODULE"); // NOI18N
4.40 - D.deb("rootDir = "+rootDir+", module = "+module+", dir = "+dir); // NOI18N
4.41 - if (dir.equals(""))
4.42 - { // NOI18N
4.43 + if (dir.equals("")) // NOI18N
4.44 + {
4.45 dir=rootDir;
4.46 - if (module != null && module.length() > 0) dir += File.separator + module;
4.47 - } else {
4.48 + if (module != null && module.length() > 0)
4.49 + dir += File.separator + module;
4.50 + }
4.51 + else
4.52 + {
4.53 if (module == null)
4.54 dir=rootDir+File.separator+dir;
4.55 else
4.56 @@ -70,7 +75,6 @@
4.57 }
4.58 if (dir.charAt(dir.length() - 1) == File.separatorChar)
4.59 dir = dir.substring(0, dir.length() - 1);
4.60 - D.deb("dir="+dir); // NOI18N
4.61 }
4.62
4.63 /**
4.64 @@ -98,7 +102,8 @@
4.65 this.dataRegex = dataRegex;
4.66 this.errorRegex = errorRegex;
4.67 this.filesByName = filesByName;
4.68 - if (args.length < 1) {
4.69 + if (args.length < 1)
4.70 + {
4.71 stderrNRListener.outputLine("Expecting a command name as an argument!"); //NOI18N
4.72 return false;
4.73 }
4.74 @@ -113,47 +118,80 @@
4.75 return !shouldFail;
4.76 }
4.77
4.78 - public void outputData(String[] elements)
4.79 + /**
4.80 + * Output data. The architecture of the VCS generic module for
4.81 + * these sort of commands is that the lines from the output of
4.82 + * running "cleartool ls" are piped into this method as an array
4.83 + * of strings for each line
4.84 + *
4.85 + * @param elements the line from running "ct ls"
4.86 + */
4.87 + public void outputData(String elements[])
4.88 {
4.89 - D.deb("elements: " + elements[0]);
4.90 + ////////////////////////////////////////////////////////////////
4.91 + // Static vars
4.92 +
4.93 + // The syntax of the lines is, for versioned files:
4.94 + // filename@@/main/branch/version# Rule: branch [-mkbranch branch]
4.95 + //
4.96 + // For local files:
4.97 + // filename
4.98 + //
4.99 + // For checked files:
4.100 + // filename@@/main/branch/CHECKEDOUT from /main/branch/version# Rule: CHECKEDOUT
4.101 + //
4.102 + // For removed checkedout files
4.103 + // filename@@/main/branch/CHECKEDOUT from /main/bugfix/2 [checkedout but removed]
4.104 + //
4.105 + // For eclipsed files
4.106 + // Y.java@@ [eclipsed]
4.107 +
4.108 + // Index of the name of the file
4.109 final int nameIndex = 0;
4.110 +
4.111 + // The separator between the name
4.112 final String statusSep = "@@";
4.113 - final String statusEndStr = "Rule: ";
4.114 - String line=elements[0];
4.115 - D.deb("match: line = "+line);
4.116 +
4.117 + String line = elements[0];
4.118 +
4.119 + // Is there an @@ in the line?
4.120 int statIndex = line.indexOf(statusSep, nameIndex);
4.121 +
4.122 if (statIndex < 0)
4.123 {
4.124 - return; // view private objects will be added by VCS as local files
4.125 + // If there was no @@, then this must be a local file.
4.126 + return;
4.127 }
4.128 - int endStatIndex = line.indexOf(statusEndStr, statIndex);
4.129 - if (endStatIndex < 0) endStatIndex = line.length() - 1;
4.130 +
4.131 + // fileInfo holds the filename and annotation of the file in an
4.132 + // array indices 0 and 1, respectively
4.133 String[] fileInfo = new String[2];
4.134 +
4.135 + // Put the name in the fileInfo
4.136 fileInfo[0] = line.substring(nameIndex, statIndex);
4.137 - File file = new File(dir+File.separator+fileInfo[0]);
4.138 - if(file != null && file.isDirectory() )
4.139 +
4.140 + // Is the file a directory? If so, add a slash to the
4.141 + // filename. I don't know why one would need to do this, but
4.142 + // we do!
4.143 + File file = new File(dir + File.separator + fileInfo[0]);
4.144 + if (file != null && file.isDirectory() )
4.145 {
4.146 fileInfo[0] += "/";
4.147 }
4.148
4.149 - String checkedOut = null;
4.150 - if ((endStatIndex + statusEndStr.length()) < line.length())
4.151 - {
4.152 - checkedOut = line.substring(endStatIndex + statusEndStr.length(), line.length());
4.153 - } else {
4.154 - checkedOut = "";
4.155 - }
4.156 -// if(checkedOut.trim()=="CHECKEDOUT")
4.157 - if(checkedOut.trim().equals("CHECKEDOUT"))
4.158 - {
4.159 - fileInfo[1] = line.substring(statIndex + statusSep.length(), endStatIndex);
4.160 - if (fileInfo[1] != null) fileInfo[1] = fileInfo[1].trim();
4.161 - }
4.162 - else
4.163 - {
4.164 - fileInfo[1] = "";
4.165 - }
4.166 + ////////////////////////////////////////////////////////////////
4.167 + // Map the status
4.168
4.169 + if (line.indexOf("[checkedout but removed]") > -1)
4.170 + fileInfo[1] = "CHECKEDOUT REMOVED";
4.171 + else if (line.indexOf("CHECKEDOUT") > -1)
4.172 + fileInfo[1] = "CHECKEDOUT";
4.173 + else if (line.indexOf("[eclipsed]") > -1)
4.174 + fileInfo[1] = "ECLIPSED";
4.175 + else
4.176 + fileInfo[1] = line.substring(statIndex + 2, line.indexOf(' ', statIndex + 2)); // Trim the rule
4.177 +
4.178 + // Put it in the output. Wierd.
4.179 filesByName.put(fileInfo[0], fileInfo);
4.180 }
4.181 }
5.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
5.2 +++ b/vcs.profiles.clearcase/src/org/netbeans/modules/vcs/profiles/clearcase/list/ClearCaseRevisionListGetter.java Fri Jun 13 09:13:36 2003 +0000
5.3 @@ -0,0 +1,349 @@
5.4 +/*
5.5 + * Sun Public License Notice
5.6 + *
5.7 + * The contents of this file are subject to the Sun Public License
5.8 + * Version 1.0 (the "License"). You may not use this file except in
5.9 + * compliance with the License. A copy of the License is available at
5.10 + * http://www.sun.com/
5.11 + *
5.12 + * The Original Code is NetBeans. The Initial Developer of the Original
5.13 + * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun
5.14 + * Microsystems, Inc. All Rights Reserved.
5.15 + */
5.16 +
5.17 +package org.netbeans.modules.vcs.profiles.clearcase.list;
5.18 +
5.19 +import java.util.*;
5.20 +
5.21 +import java.io.*;
5.22 +
5.23 +import org.openide.filesystems.*;
5.24 +import org.openide.util.NbBundle;
5.25 +
5.26 +import org.netbeans.modules.vcscore.Variables;
5.27 +import org.netbeans.modules.vcscore.VcsFileSystem;
5.28 +import org.netbeans.modules.vcscore.VcsFactory;
5.29 +import org.netbeans.modules.vcscore.cmdline.VcsAdditionalCommand;
5.30 +import org.netbeans.modules.vcscore.commands.*;
5.31 +import org.netbeans.modules.vcscore.versioning.RevisionItem;
5.32 +import org.netbeans.modules.vcscore.versioning.RevisionList;
5.33 +import org.netbeans.modules.vcscore.versioning.impl.NumDotRevisionItem;
5.34 +import org.netbeans.modules.vcscore.versioning.impl.NumDotRevisionList;
5.35 +import org.netbeans.modules.vcscore.util.VcsUtilities;
5.36 +
5.37 +public class ClearCaseRevisionListGetter implements VcsAdditionalCommand
5.38 +{
5.39 +
5.40 + private ArrayList revisionItems = new ArrayList();
5.41 + private ArrayList lastRevisionItems = null;
5.42 + private VcsFileSystem fileSystem = null;
5.43 + private VcsCommand logCmd = null;
5.44 + private CommandOutputListener stdoutNRListener = null;
5.45 + private CommandOutputListener stderrNRListener = null;
5.46 +
5.47 + public ClearCaseRevisionListGetter()
5.48 + {
5.49 + }
5.50 +
5.51 + public void setFileSystem(VcsFileSystem fileSystem)
5.52 + {
5.53 + this.fileSystem = fileSystem;
5.54 + }
5.55 +
5.56 + /**
5.57 + * From the VcsAdditionalCommand interface...
5.58 + *
5.59 + * Executes the history command to get the logging informations.
5.60 + * @param vars variables needed to run the clearcase commands
5.61 + * @param args the arguments,
5.62 + * @param stdoutNRListener listener of the standard output of the command
5.63 + * @param stderrNRListener listener of the error output of the command
5.64 + * @param stdoutListener listener of the standard output of the command which
5.65 + * satisfies regex <CODE>dataRegex</CODE>
5.66 + * @param dataRegex the regular expression for parsing the standard output
5.67 + * @param stderrListener listener of the error output of the command which
5.68 + * satisfies regex <CODE>errorRegex</CODE>
5.69 + * @param errorRegex the regular expression for parsing the error output
5.70 + * @return true if the command was succesfull,
5.71 + * false if some error has occured.
5.72 + */
5.73 + public boolean exec(final Hashtable vars, String[] args,
5.74 + CommandOutputListener stdoutNRListener, CommandOutputListener stderrNRListener,
5.75 + CommandDataOutputListener stdoutListener, String dataRegex,
5.76 + CommandDataOutputListener stderrListener, String errorRegex) {
5.77 + // Wire the listeners
5.78 + this.stdoutNRListener = stdoutNRListener;
5.79 + this.stderrNRListener = stderrNRListener;
5.80 +
5.81 + System.out.println("Calling revision list getter for " + args[0]);
5.82 +
5.83 + InputStream istream = null;
5.84 +
5.85 + try {
5.86 + Process process = Runtime.getRuntime().exec(
5.87 + new String[] {
5.88 + "cleartool",
5.89 + "lshistory",
5.90 + "-fmt",
5.91 + "\"%Nd\" \"%u\" %o \"%f\" \"%Vn\" \"%En\";\n",
5.92 + args[0]
5.93 + }
5.94 + );
5.95 +
5.96 + istream = process.getInputStream();
5.97 + InputStream errstream = process.getErrorStream();
5.98 +
5.99 + BufferedReader es = new BufferedReader(new InputStreamReader(errstream));
5.100 + ClearCaseRevisionList revisionList = new ClearCaseRevisionList();
5.101 +
5.102 + revisionList.processRevisionStream(istream);
5.103 +
5.104 +
5.105 + // This is incomprehensible. We turn it into a byte array
5.106 + // for hex encoding??? WHY?
5.107 + stdoutListener.outputData(new String[] {VcsUtilities.encodeValue(revisionList)});
5.108 + // Dump the error stream
5.109 + String line;
5.110 + while ((line = es.readLine()) != null)
5.111 + {
5.112 + stderrNRListener.outputLine(line);
5.113 + }
5.114 + } catch (IOException ioe) {
5.115 + ioe.printStackTrace();
5.116 +
5.117 + // Dump rest of output stream for debugging
5.118 + if (istream != null)
5.119 + {
5.120 + try {
5.121 + BufferedReader is = new BufferedReader(new InputStreamReader(istream));
5.122 + String line;
5.123 +
5.124 + while ((line = is.readLine()) != null)
5.125 + {
5.126 + System.out.println(line);
5.127 + }
5.128 + } catch (IOException ioe2) {
5.129 + // oh well
5.130 + }
5.131 + }
5.132 +
5.133 + return false;
5.134 + }
5.135 +
5.136 + return true;
5.137 + }
5.138 +
5.139 + static class ClearCaseRevisionItem extends RevisionItem
5.140 + {
5.141 + String date;
5.142 + String uname;
5.143 + String operation;
5.144 + String checkout;
5.145 + String version;
5.146 + String entityName;
5.147 + String comment;
5.148 + String branch;
5.149 +
5.150 + ClearCaseRevisionList list;
5.151 +
5.152 + public ClearCaseRevisionItem(String date,
5.153 + String uname,
5.154 + String operation,
5.155 + String checkout,
5.156 + String version,
5.157 + String entityName,
5.158 + String comment,
5.159 + ClearCaseRevisionList list)
5.160 + {
5.161 + super(version);
5.162 +
5.163 + this.date = date;
5.164 + this.uname = uname;
5.165 + this.operation = operation;
5.166 + this.checkout = checkout;
5.167 + this.version = version;
5.168 + this.entityName = entityName;
5.169 + this.comment = comment;
5.170 + this.list = list;
5.171 +
5.172 + int pos;
5.173 +
5.174 + if (version != null && (pos = version.lastIndexOf('/')) != -1)
5.175 + {
5.176 + this.branch = version.substring(0, pos);
5.177 + }
5.178 + }
5.179 + public RevisionItem getNextItem()
5.180 + {
5.181 + SortedSet tailSet = list.tailSet(this);
5.182 + if (tailSet.first() != this)
5.183 + throw new RuntimeException("I'm confused");
5.184 + Iterator iterator = tailSet.iterator();
5.185 + iterator.next(); // Skip past self
5.186 + if (!iterator.hasNext())
5.187 + {
5.188 + return null;
5.189 + }
5.190 +
5.191 + ClearCaseRevisionItem result = (ClearCaseRevisionItem) iterator.next();
5.192 + if (result.operation.equals("checkin")
5.193 + && result.branch.equals(this.branch))
5.194 + return result;
5.195 + else
5.196 + return null;
5.197 + }
5.198 +
5.199 + public boolean isBranch()
5.200 + {
5.201 + boolean result = operation.equals("mkbranch") || operation.equals("mkelem");
5.202 + return result;
5.203 + }
5.204 +
5.205 + protected int cmpRev(String revision)
5.206 + {
5.207 + return revision.compareTo(this.getRevision());
5.208 + }
5.209 +
5.210 + public RevisionItem addRevision(String revision)
5.211 + {
5.212 + throw new RuntimeException("Unsupported method");
5.213 + }
5.214 +
5.215 + public RevisionItem addBranch(String branch)
5.216 + {
5.217 + throw new RuntimeException("Unsupported method");
5.218 + }
5.219 +
5.220 + private void readObject(java.io.ObjectInputStream in)
5.221 + throws ClassNotFoundException,
5.222 + java.io.IOException
5.223 + {
5.224 + in.defaultReadObject();
5.225 + System.out.println("Deserializing " + getRevision());
5.226 + }
5.227 + }
5.228 +
5.229 + static class ClearCaseRevisionList extends RevisionList
5.230 + {
5.231 + HashMap revisionTable = new HashMap();
5.232 +
5.233 + public void processRevisionStream(InputStream istream)
5.234 + throws IOException
5.235 + {
5.236 + StreamTokenizer tok = new StreamTokenizer(istream);
5.237 +
5.238 + tok.eolIsSignificant(false);
5.239 +
5.240 + // Date processing
5.241 + tok.wordChars('.', '.');
5.242 + tok.wordChars('-', '-');
5.243 + tok.wordChars(':', ':');
5.244 + tok.wordChars('0', '9');
5.245 +
5.246 + // Version path processing
5.247 + tok.wordChars('/', '/');
5.248 +
5.249 + String lastDate = null;
5.250 +
5.251 + while (true)
5.252 + {
5.253 + int ttype = tok.nextToken();
5.254 + if (ttype == StreamTokenizer.TT_EOF)
5.255 + break;
5.256 +
5.257 + String date = tok.sval;
5.258 + ttype = tok.nextToken();
5.259 + String username = tok.sval;
5.260 + ttype = tok.nextToken();
5.261 + String operation = tok.sval;
5.262 + ttype = tok.nextToken();
5.263 + String checkout = tok.sval;
5.264 + ttype = tok.nextToken();
5.265 + String version = tok.sval;
5.266 + ttype = tok.nextToken();
5.267 + String entityName = tok.sval;
5.268 + StringBuffer comment = new StringBuffer();
5.269 +
5.270 + ttype = tok.nextToken();
5.271 +
5.272 + /*
5.273 + // Now we read the comment
5.274 + while ((ttype = tok.nextToken()) == StreamTokenizer.TT_WORD)
5.275 + {
5.276 + comment.append(tok.sval);
5.277 + }
5.278 + */
5.279 + if (lastDate == null || !lastDate.equals(date))
5.280 + {
5.281 + ClearCaseRevisionItem item = new ClearCaseRevisionItem(
5.282 + date,
5.283 + username,
5.284 + operation,
5.285 + checkout,
5.286 + version,
5.287 + entityName,
5.288 + comment.toString(),
5.289 + this);
5.290 + this.add(item);
5.291 + if (revisionTable.get(version) != null)
5.292 + System.out.println("WARNING: replacing revision entry");
5.293 +
5.294 + this.revisionTable.put(version, item);
5.295 + }
5.296 + lastDate = date;
5.297 +
5.298 + if (ttype != ';')
5.299 + throw new IOException("Malformed comment, expected ';', got " + (char) ttype + "(" + ttype + ") last token " + entityName);
5.300 + }
5.301 +
5.302 + System.out.println("Processed " + revisionTable.size() + " items");
5.303 +
5.304 + System.out.println("Here is everything in order");
5.305 + Iterator iterator = this.iterator();
5.306 + while (iterator.hasNext())
5.307 + {
5.308 + ClearCaseRevisionItem item = (ClearCaseRevisionItem) iterator.next();
5.309 + System.out.println(item.operation + ":" + item.getRevision());
5.310 + }
5.311 +
5.312 + System.out.println("Here are the branches");
5.313 + iterator = this.iterator();
5.314 + while (iterator.hasNext())
5.315 + {
5.316 + ClearCaseRevisionItem item = (ClearCaseRevisionItem) iterator.next();
5.317 + if (item.isBranch())
5.318 + {
5.319 + System.out.println(item.getRevision() + " contains subrevisions: " + containsSubRevisions(item.getRevision()));
5.320 +
5.321 + ClearCaseRevisionItem next = (ClearCaseRevisionItem) item.getNextItem();
5.322 +
5.323 + while (next != null)
5.324 + {
5.325 + System.out.println("On " + item.getRevision() +
5.326 + " is " +
5.327 + next.getRevision());
5.328 +
5.329 + next = (ClearCaseRevisionItem) next.getNextItem();
5.330 + }
5.331 + }
5.332 + }
5.333 + }
5.334 +
5.335 + /**
5.336 + * for any revision string that you get you should return true
5.337 + * only if there exists some revision that is in this branch.
5.338 + */
5.339 + public boolean containsSubRevisions(String revision)
5.340 + {
5.341 + ClearCaseRevisionItem item = (ClearCaseRevisionItem) revisionTable.get(revision);
5.342 + boolean result = item.isBranch();
5.343 + return result;
5.344 + }
5.345 + private void readObject(java.io.ObjectInputStream in) throws ClassNotFoundException, java.io.IOException
5.346 + {
5.347 + in.defaultReadObject();
5.348 +
5.349 + System.out.println("Deserializing, now have " + size() + " items");
5.350 + }
5.351 + }
5.352 +}