001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.form;
042:
043: import java.awt.EventQueue;
044: import java.io.BufferedReader;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.InputStreamReader;
048: import java.io.OutputStream;
049: import java.net.URL;
050: import java.util.ArrayList;
051: import java.util.LinkedList;
052: import java.util.List;
053: import org.netbeans.api.java.classpath.ClassPath;
054: import org.netbeans.modules.refactoring.spi.BackupFacility;
055: import org.netbeans.modules.refactoring.spi.RefactoringElementImplementation;
056: import org.netbeans.modules.refactoring.spi.SimpleRefactoringElementImplementation;
057: import org.netbeans.modules.refactoring.spi.Transaction;
058: import org.openide.filesystems.FileLock;
059: import org.openide.filesystems.FileObject;
060: import org.openide.filesystems.URLMapper;
061: import org.openide.loaders.DataObject;
062: import org.openide.loaders.DataObjectNotFoundException;
063: import org.openide.text.PositionBounds;
064: import org.openide.util.Exceptions;
065: import org.openide.util.Lookup;
066: import org.openide.util.NbBundle;
067:
068: /**
069: * This class does the actual refactoring changes for one form - updates the
070: * form, regenerates code, updates properties files for i18n, etc. Multiple
071: * different instances (updates) can be created and executed for one refactoring
072: * (all kept in RefacoringInfo).
073: *
074: * @author Tomas Pavek
075: */
076: public class FormRefactoringUpdate extends
077: SimpleRefactoringElementImplementation implements Transaction {
078:
079: /**
080: * Information about the performed refactoring.
081: */
082: private RefactoringInfo refInfo;
083:
084: /**
085: * RefactoringElement used in the preview, but doing nothing.
086: */
087: private RefactoringElementImplementation previewElement;
088:
089: /**
090: * Java file of a form affected by the refactoring.
091: */
092: private FileObject changingFile;
093:
094: /**
095: * DataObject of the changed file. Has changedFile as primary file at the
096: * beginning, but may get a different one later (e.g. if moved).
097: */
098: private FormDataObject formDataObject;
099:
100: /**
101: * FormEditor of the updated form. Either taken from the FormDataObject
102: * (typically when already opened), or created temporarily just to do the
103: * update. See prepareForm method.
104: */
105: private FormEditor formEditor;
106:
107: private boolean loadingFailed;
108:
109: /**
110: * Whether a change in guarded code was requested by java refactoring.
111: */
112: private boolean guardedCodeChanging;
113:
114: private boolean transactionDone;
115:
116: private boolean formFileRenameDone;
117:
118: private List<RefactoringElementImplementation> preFileChanges;
119:
120: private List<BackupFacility.Handle> backups;
121:
122: // -----
123:
124: public FormRefactoringUpdate(RefactoringInfo refInfo,
125: FileObject changingFile) {
126: this .refInfo = refInfo;
127: this .changingFile = changingFile;
128: try {
129: DataObject dobj = DataObject.find(changingFile);
130: if (dobj instanceof FormDataObject) {
131: formDataObject = (FormDataObject) dobj;
132: }
133: } catch (DataObjectNotFoundException ex) {
134: assert false;
135: }
136: }
137:
138: FormDataObject getFormDataObject() {
139: return formDataObject;
140: }
141:
142: RefactoringElementImplementation getPreviewElement(/*String displayText*/) {
143: if (previewElement == null) {
144: previewElement = new PreviewElement(changingFile/*, displayText*/);
145: }
146: return previewElement;
147: }
148:
149: void setGaurdedCodeChanging(boolean b) {
150: guardedCodeChanging = b;
151: }
152:
153: boolean isGuardedCodeChanging() {
154: return guardedCodeChanging;
155: }
156:
157: public void addPrecedingFileChange(
158: RefactoringElementImplementation change) {
159: if (preFileChanges == null) {
160: preFileChanges = new LinkedList<RefactoringElementImplementation>();
161: }
162: preFileChanges.add(change);
163: }
164:
165: // -----
166:
167: // Transaction (registered via RefactoringElementsBag.registerTransaction)
168: public void commit() {
169: if (previewElement != null && !previewElement.isEnabled()) {
170: return;
171: }
172:
173: // As "transactions" we do updates for changes affecting only the
174: // content of the source file, not changing the file's name or location.
175: // Our transaction is called after retouche commits its changes to the
176: // source. After all transactions are done, the source file is saved
177: // automatically.
178:
179: switch (refInfo.getChangeType()) {
180: case VARIABLE_RENAME:
181: renameMetaComponent(refInfo.getOldName(), refInfo
182: .getNewName());
183: transactionDone = true;
184: break;
185: case CLASS_RENAME: // renaming a component class used in the form
186: if (!refInfo.getPrimaryFile().equals(changingFile)) {
187: componentClassRename(refInfo.getOldName(), refInfo
188: .getNewName());
189: transactionDone = true;
190: }
191: break;
192: case CLASS_MOVE: // moving a component class used in the form
193: if (!refInfo.getPrimaryFile().equals(changingFile)) {
194: componentChange(refInfo.getOldName(), refInfo
195: .getNewName());
196: transactionDone = true;
197: }
198: break;
199: //do nothing otherwise - could be just redundantly registered by the guarded handler
200: case CLASS_DELETE: // deleting form (we only need to backup the form file for undo)
201: saveFormForUndo();
202: transactionDone = true;
203: break;
204: }
205:
206: }
207:
208: // Transaction (registered via RefactoringElementsBag.registerTransaction)
209: public void rollback() {
210: if (previewElement != null && !previewElement.isEnabled()) {
211: return;
212: }
213: undoFromBackups();
214: /* switch (refInfo.getChangeType()) {
215: case VARIABLE_RENAME:
216: renameMetaComponent(refInfo.getNewName(), refInfo.getOldName());
217: break;
218: case CLASS_RENAME: // renaming a component class used in the form
219: if (!refInfo.getPrimaryFile().equals(changingFile)) {
220: componentClassRename(refInfo.getNewName(), refInfo.getOldName());
221: }
222: break;
223: case CLASS_MOVE: // moving a component class used in the form
224: if (!refInfo.getPrimaryFile().equals(changingFile)) {
225: componentChange(refInfo.getNewName(), refInfo.getOldName());
226: }
227: break;
228: } */
229: }
230:
231: // RefactoringElementImplementation (registered via RefactoringElementsBag.addFileChange)
232: public void performChange() {
233: if (previewElement != null && !previewElement.isEnabled()) {
234: return;
235: }
236: if (transactionDone) { // could be registered redundantly as file change
237: processCustomCode();
238: return;
239: }
240:
241: // As "file changes" we do updates that react on changes of the source
242: // file's name or location. We need the source file to be already
243: // renamed/moved. The file changes are run after the "transactions".
244:
245: if (preFileChanges != null) {
246: for (RefactoringElementImplementation change : preFileChanges) {
247: change.performChange();
248: }
249: }
250:
251: switch (refInfo.getChangeType()) {
252: case CLASS_RENAME: // renaming the form itself
253: if (refInfo.getPrimaryFile().equals(changingFile)) {
254: formRename();
255: }
256: break;
257: case CLASS_MOVE: // moving the form itself
258: if (refInfo.getPrimaryFile().equals(changingFile)
259: && prepareForm(false)) {
260: formMove();
261: }
262: break;
263: case PACKAGE_RENAME:
264: case FOLDER_RENAME:
265: packageRename();
266: break;
267: }
268:
269: processCustomCode();
270: }
271:
272: // RefactoringElementImplementation (registered via RefactoringElementsBag.addFileChange)
273: @Override
274: public void undoChange() {
275: if (previewElement != null && !previewElement.isEnabled()) {
276: return;
277: }
278: if (transactionDone) { // could be registered redundantly as file change
279: return;
280: }
281:
282: undoFromBackups();
283:
284: if (preFileChanges != null) {
285: for (RefactoringElementImplementation change : preFileChanges) {
286: change.undoChange();
287: }
288: }
289: }
290:
291: // -----
292:
293: private void renameMetaComponent(String oldName, String newName) {
294: if (prepareForm(true)) {
295: RADComponent metacomp = formEditor.getFormModel()
296: .findRADComponent(oldName);
297: if (metacomp != null) {
298: saveFormForUndo();
299: saveResourcesForContentChangeUndo();
300: metacomp.setName(newName);
301: updateForm(false);
302: }
303: }
304: }
305:
306: private void formRename(/*boolean saveAll*/) {
307: if (prepareForm(true)) {
308: saveFormForUndo();
309: saveResourcesForFormRenameUndo();
310: ResourceSupport.formRenamed(formEditor.getFormModel(),
311: refInfo.getOldName());
312: updateForm(true);
313: }
314: }
315:
316: private void componentClassRename(String oldName, String newName) {
317: FileObject renamedFile = refInfo.getPrimaryFile();
318: String pkg = ClassPath.getClassPath(renamedFile,
319: ClassPath.SOURCE).getResourceName(
320: renamedFile.getParent(), '.', false);
321: String oldClassName = (pkg != null && pkg.length() > 0) ? pkg
322: + "." + oldName : oldName; // NOI18N
323: String newClassName = (pkg != null && pkg.length() > 0) ? pkg
324: + "." + newName : newName; // NOI18N
325: componentChange(oldClassName, newClassName);
326: }
327:
328: private void formMove(/*final boolean saveAll*/) {
329: final FormEditorSupport fes = formDataObject
330: .getFormEditorSupport();
331: if (fes.isOpened()) {
332: EventQueue.invokeLater(new Runnable() {
333: public void run() {
334: formEditor = fes.reloadFormEditor();
335: formMove2(/*saveAll*/);
336: }
337: });
338: } else {
339: assert !formEditor.isFormLoaded();
340: formMove2(/*saveAll*/);
341: }
342: }
343:
344: private void formMove2(/*boolean saveAll*/) {
345: if (prepareForm(true)) {
346: saveFormForUndo();
347: FileObject oldFolder = changingFile.getParent();
348: saveResourcesForFormMoveUndo(oldFolder);
349: ResourceSupport.formMoved(formEditor.getFormModel(),
350: oldFolder);
351: updateForm(true);
352: }
353: }
354:
355: private void componentChange(String oldClassName,
356: String newClassName) {
357: FormEditorSupport fes = formDataObject.getFormEditorSupport();
358: if (fes.isOpened()) {
359: fes.closeFormEditor();
360: }
361: replaceClassOrPkgName(oldClassName, newClassName, false);
362: replaceShortClassName(oldClassName, newClassName);
363: }
364:
365: private boolean replaceShortClassName(String oldName, String newName) {
366: if (oldName.contains(".")) { // NOI18N
367: String shortOldName = oldName.substring(oldName
368: .lastIndexOf('.') + 1);
369: String shortNewName = newName.substring(newName
370: .lastIndexOf('.') + 1);
371: if (!shortNewName.equals(shortOldName)) {
372: return replaceClassOrPkgName(shortOldName, newName,
373: false); // (intentionally replace with FQN)
374: }
375: }
376: return false;
377: }
378:
379: private void packageRename() {
380: FormEditorSupport fes = formDataObject.getFormEditorSupport();
381: if (fes.isOpened()) {
382: fes.closeFormEditor();
383: }
384: if (replaceClassOrPkgName(refInfo.getOldName(), refInfo
385: .getNewName(), true)
386: && !isGuardedCodeChanging()) {
387: // some package references in resource were changed in the form file
388: // (not class names since no change in guarded code came from java
389: // refactoring) and because no component has changed we can load the
390: // form and regenerate to get the new resource names into code
391: updateForm(true);
392: }
393: }
394:
395: /**
396: * Tries to update the fragments of custom code in the .form file according
397: * to the refactoring change. The implementation is quite simple and
398: * super-ugly. It goes through the form file, finds relevant attributes,
399: * and blindly replaces given "old name" with a "new name". Should mostly
400: * work when a component variable or class is renamed. Should be enough
401: * though, since the usage of custom code is quite limited.
402: */
403: private void processCustomCode() {
404: if (isGuardedCodeChanging() && !formFileRenameDone) {
405: String oldName = refInfo.getOldName();
406: String newName = refInfo.getNewName();
407: if (oldName != null && newName != null) {
408: boolean replaced = replaceClassOrPkgName(oldName,
409: newName, false);
410: // also try to replace short class name
411: switch (refInfo.getChangeType()) {
412: case CLASS_RENAME:
413: case CLASS_MOVE:
414: replaced |= replaceShortClassName(oldName, newName);
415: break;
416: }
417: if (replaced) { // regenerate the code
418: // need to reload the form from file
419: final FormEditorSupport fes = formDataObject
420: .getFormEditorSupport();
421: if (fes.isOpened()) {
422: EventQueue.invokeLater(new Runnable() {
423: public void run() {
424: formEditor = fes.reloadFormEditor();
425: updateForm(true);
426: }
427: });
428: } else {
429: if (formEditor != null
430: && formEditor.isFormLoaded()) {
431: formEditor.closeForm();
432: }
433: if (prepareForm(true)) {
434: updateForm(true);
435: }
436: }
437: }
438: formFileRenameDone = false; // not to block redo
439: }
440: }
441: }
442:
443: // -----
444:
445: /**
446: * Regenerate code and save.
447: */
448: private void updateForm(boolean saveAll) {
449: if (!prepareForm(true)) {
450: return;
451: }
452: // hack: regenerate code immediately
453: formEditor.getFormModel().fireFormChanged(true);
454: FormEditorSupport fes = getFormDataObject()
455: .getFormEditorSupport();
456: try {
457: if (!fes.isOpened()) {
458: // the form is not opened, just loaded aside to do this refactoring
459: // update (not held from FormEditorSupport); so we must save the
460: // form always - it would not get save with refactoring
461: formEditor.saveFormData(); // TODO should save form only if there was a change
462: if (saveAll) { // a post-refactoring change that would not be saved by refactoring
463: fes.saveSourceOnly();
464: }
465: formEditor.closeForm();
466: } else if (saveAll) { // a post-refactoring change that would not be saved
467: fes.saveDocument();
468: }
469: } catch (Exception ex) {
470: Exceptions.printStackTrace(ex);
471: }
472: }
473:
474: boolean prepareForm(boolean load) {
475: if (formDataObject != null) {
476: FormEditor fe = formDataObject.getFormEditorSupport()
477: .getFormEditor();
478: if (fe != null) { // use the current FormEditor (might change due to reload after undo)
479: formEditor = fe;
480: } else if (formEditor == null) { // create a disconnected form editor
481: formEditor = new FormEditor(formDataObject);
482: }
483: }
484: if (formEditor != null) {
485: if (formEditor.isFormLoaded() || !load) {
486: return true;
487: } else if (!loadingFailed) {
488: if (formEditor.loadForm()) {
489: return true;
490: } else {
491: loadingFailed = true;
492: }
493: }
494: }
495: return false;
496: }
497:
498: private void saveFormForUndo() {
499: saveForUndo(formDataObject.getFormFile());
500: // java file is backed up by java refactoring
501: }
502:
503: private void saveResourcesForContentChangeUndo() {
504: for (URL url : ResourceSupport
505: .getFilesForContentChangeBackup(formEditor
506: .getFormModel())) {
507: saveForUndo(url);
508: }
509: }
510:
511: private void saveResourcesForFormRenameUndo() {
512: for (URL url : ResourceSupport
513: .getFilesForFormRenameBackup(formEditor.getFormModel())) {
514: saveForUndo(url);
515: }
516: }
517:
518: private void saveResourcesForFormMoveUndo(FileObject oldFolder) {
519: for (URL url : ResourceSupport.getFilesForFormMoveBackup(
520: formEditor.getFormModel(), oldFolder)) {
521: saveForUndo(url);
522: }
523: }
524:
525: private void saveForUndo(final URL url) {
526: FileObject file = URLMapper.findFileObject(url);
527: BackupFacility.Handle id;
528: if (file != null) {
529: try {
530: id = BackupFacility.getDefault().backup(file);
531: } catch (IOException ex) {
532: Exceptions.printStackTrace(ex);
533: return;
534: }
535: } else { // file does not exist - will be created; to undo we must delete it
536: id = new BackupFacility.Handle() {
537: public void restore() throws IOException {
538: FileObject file = URLMapper.findFileObject(url);
539: if (file != null) {
540: file.delete();
541: }
542: }
543: };
544: }
545: if (backups == null) {
546: backups = new ArrayList<BackupFacility.Handle>();
547: }
548: backups.add(id);
549: }
550:
551: private void saveForUndo(FileObject file) {
552: try {
553: BackupFacility.Handle id = BackupFacility.getDefault()
554: .backup(file);
555: if (backups == null) {
556: backups = new ArrayList<BackupFacility.Handle>();
557: }
558: backups.add(id);
559: } catch (IOException ex) {
560: Exceptions.printStackTrace(ex);
561: }
562: }
563:
564: private void undoFromBackups() {
565: if (backups != null) {
566: try {
567: for (BackupFacility.Handle id : backups) {
568: id.restore();
569: }
570: } catch (IOException ex) {
571: Exceptions.printStackTrace(ex);
572: }
573: backups.clear();
574: }
575: }
576:
577: // -----
578:
579: private static class PreviewElement extends
580: SimpleRefactoringElementImplementation {
581: private FileObject file;
582:
583: PreviewElement(FileObject file) {
584: this .file = file;
585: }
586:
587: public String getText() {
588: return "GUI form update"; // NOI18N
589: }
590:
591: public String getDisplayText() {
592: return NbBundle.getMessage(FormRefactoringUpdate.class,
593: "CTL_RefactoringUpdate1"); // NOI18N
594: }
595:
596: public void performChange() {
597: }
598:
599: public Lookup getLookup() {
600: return Lookup.EMPTY;
601: }
602:
603: public FileObject getParentFile() {
604: return file;
605: }
606:
607: public PositionBounds getPosition() {
608: return null;
609: }
610: }
611:
612: // -----
613:
614: // RefactoringElementImplementation
615: public String getText() {
616: return "GUI form update";
617: }
618:
619: // RefactoringElementImplementation
620: public String getDisplayText() {
621: return NbBundle.getMessage(FormRefactoringUpdate.class,
622: "CTL_RefactoringUpdate2"); // NOI18N
623: }
624:
625: // RefactoringElementImplementation
626: public Lookup getLookup() {
627: return Lookup.EMPTY;
628: }
629:
630: // RefactoringElementImplementation
631: public FileObject getParentFile() {
632: return changingFile;
633: }
634:
635: // RefactoringElementImplementation
636: public PositionBounds getPosition() {
637: return null;
638: }
639:
640: // -----
641:
642: /**
643: * Elements and attributes that are used to search in when trying to replace
644: * a non-FQN name (identifier) in custom code of a form.
645: */
646: private static final String[] FORM_ELEMENTS_ATTRS = {
647: "<Component ", " class=\"", // NOI18N
648: "<AuxValue name=\"JavaCodeGenerator_", " value=\"", // NOI18N
649: "<Property ", " preCode=\"", // NOI18N
650: "<Property ", " postCode=\"", // NOI18N
651: "<Connection ", " code=\"" // NOI18N
652: };
653:
654: /**
655: * Replace the class or package name directly in the form file. It is
656: * important not to cause other diff in the file.
657: * ... A provisional strawman solution using textual replace, yuck ...
658: * But should work fine with the *current* form file format and with
659: * fully qualified class names, also covering the user's code (though
660: * users will probably not use FQN.)
661: * @return whether anything was changed in teh form file
662: */
663: private boolean replaceClassOrPkgName(String oldName,
664: String newName, boolean pkgName) {
665: FileObject formFile = formDataObject.getFormFile();
666: FileLock lock = null;
667: OutputStream os = null;
668: InputStream is = null;
669: try {
670: lock = formFile.lock();
671:
672: String[] oldStr;
673: String[] newStr;
674: boolean shortName;
675: if (pkgName) {
676: oldName = oldName + "."; // NOI18N
677: newName = newName + "."; // NOI18N
678: oldStr = new String[] { oldName,
679: oldName.replace('.', '/') };
680: newStr = new String[] { newName,
681: newName.replace('.', '/') };
682: shortName = false;
683: } else {
684: shortName = !oldName.contains("."); // NOI18N
685: oldStr = new String[] { oldName };
686: newStr = new String[] { newName };
687: }
688:
689: String encoding = "UTF-8"; // NOI18N
690: String outString;
691: is = formFile.getInputStream();
692: BufferedReader reader = new BufferedReader(
693: new InputStreamReader(is, encoding));
694: if (!shortName) {
695: // With fully qualified name we can safely do plain textual
696: // search/replace over the file and get all changes covered
697: // (component class name elements, custom code, property editors,
698: // also icons and resource bundles if package name is changed, etc).
699: NameReplacer rep = new NameReplacer(oldStr, newStr,
700: (int) formFile.getSize());
701: String line = reader.readLine();
702: while (line != null) {
703: rep.append(line);
704: line = reader.readLine();
705: if (line != null) {
706: rep.append("\n"); // NOI18N
707: }
708: }
709: outString = rep.getResult(); // will also process the last char
710: if (!rep.anythingChanged()) {
711: return false;
712: }
713: } else {
714: // The replaced name is short with no '.', so it is too risky
715: // to do plain search/replace over the entire file content.
716: // Search only in the specific elements and attributes.
717: StringBuilder buf = new StringBuilder((int) formFile
718: .getSize());
719: boolean anyChange = false;
720: String line = reader.readLine();
721: while (line != null) {
722: String trimLine = line.trim();
723: for (int i = 0; i < FORM_ELEMENTS_ATTRS.length; i += 2) {
724: if (trimLine.startsWith(FORM_ELEMENTS_ATTRS[i])) {
725: String attr = FORM_ELEMENTS_ATTRS[i + 1];
726: int idx = line.indexOf(attr);
727: if (idx > 0) {
728: // get the value of the attribute - string enclosed in ""
729: int idx1 = idx1 = idx + attr.length();
730: if (!attr.endsWith("\"")) { // NOI18N
731: while (idx1 < line.length()
732: && line.charAt(idx1) != '\"') { // NOI18N
733: idx1++;
734: }
735: idx1++;
736: }
737: int idx2 = idx1;
738: while (idx2 < line.length()
739: && line.charAt(idx2) != '\"') { // NOI18N
740: idx2++;
741: }
742: if (idx1 < line.length()
743: && idx2 < line.length()) {
744: String sub = line.substring(idx1,
745: idx2);
746: if (sub.contains(oldName)) {
747: NameReplacer rep = new NameReplacer(
748: oldStr, newStr, sub
749: .length());
750: rep.append(sub);
751: sub = rep.getResult(); // will also process the last char
752: if (rep.anythingChanged()) {
753: line = line.substring(0,
754: idx1)
755: + sub
756: + line
757: .substring(idx2);
758: anyChange = true;
759: }
760: }
761: }
762: }
763: }
764: }
765: buf.append(line);
766: line = reader.readLine();
767: if (line != null) {
768: buf.append("\n"); // NOI18N
769: }
770: }
771: if (!anyChange) {
772: return false;
773: }
774: outString = buf.toString();
775: }
776:
777: saveForUndo(formFile);
778:
779: is.close();
780: is = null;
781:
782: os = formFile.getOutputStream(lock);
783: os.write(outString.getBytes(encoding));
784: } catch (Exception ex) {
785: Exceptions.printStackTrace(ex);
786: return false;
787: } finally {
788: try {
789: if (is != null) {
790: is.close();
791: }
792: if (os != null) {
793: os.close();
794: }
795: } catch (IOException ex) { // ignore
796: }
797: if (lock != null) {
798: lock.releaseLock();
799: }
800: }
801: formFileRenameDone = true; // we don't need to do processCustomCode
802: return true;
803: }
804:
805: private static class NameReplacer {
806: private String[] toReplace;
807: private String[] replaceWith;
808: private int[] matchCounts;
809:
810: private StringBuilder buffer;
811: private StringBuilder pendingChars;
812: private char lastChar;
813:
814: private boolean anyChange;
815: private boolean ended;
816:
817: public NameReplacer(String[] toReplace, String[] replaceWith,
818: int len) {
819: this .toReplace = toReplace;
820: for (String s : toReplace) {
821: assert s != null && s.length() > 0;
822: }
823: for (String s : replaceWith) {
824: assert s != null && s.length() > 0;
825: }
826: this .replaceWith = replaceWith;
827: this .pendingChars = new StringBuilder(50);
828: this .buffer = new StringBuilder(len);
829: matchCounts = new int[toReplace.length];
830: }
831:
832: public void append(String str) {
833: assert !ended;
834: for (int i = 0; i < str.length(); i++) {
835: append(str.charAt(i));
836: }
837: }
838:
839: public String getResult() {
840: for (int i = 0; i < toReplace.length; i++) {
841: String template = toReplace[i];
842: int count = matchCounts[i];
843: if (count == template.length()) {
844: replace(i);
845: break;
846: }
847: }
848: writePendingChars();
849: ended = true;
850: return buffer.toString();
851: }
852:
853: /**
854: * Returns whether any replacement happened in written characters, i.e.
855: * whether the output differs from the input. Note this method should be
856: * called after all is written and getResult() called - to be sure the
857: * last character was processed properly.
858: * @return true if some chars were replaced in the text passed in via
859: * append method
860: */
861: public boolean anythingChanged() {
862: return anyChange;
863: }
864:
865: private void append(char c) {
866: int completeMatch = -1; // index of template string
867: boolean charMatch = false;
868: for (int i = 0; i < toReplace.length; i++) {
869: String template = toReplace[i];
870: int count = matchCounts[i];
871: if (count == template.length()) {
872: if (canEndHere(c)) { // so the name is not just a subset of a longer name
873: completeMatch = i;
874: break;
875: } else {
876: matchCounts[i] = 0;
877: continue;
878: }
879: }
880: if (template.charAt(count) == c) {
881: if (count > 0 || canStartHere()) { // not to start in the middle of a longer name
882: matchCounts[i] = count + 1;
883: charMatch = true;
884: }
885: } else {
886: matchCounts[i] = 0;
887: }
888: }
889:
890: if (completeMatch >= 0) {
891: replace(completeMatch);
892: buffer.append(c); // the first char after can't match (names can't follow without a gap)
893: } else if (charMatch) {
894: pendingChars.append(c);
895: } else {
896: writePendingChars();
897: buffer.append(c);
898: }
899:
900: lastChar = c;
901: }
902:
903: private boolean canStartHere() {
904: return lastChar != '.'
905: && lastChar != '/'
906: && (lastChar <= ' ' || !Character
907: .isJavaIdentifierPart(lastChar));
908: // surprisingly 0 is considered as valid char
909: }
910:
911: private boolean canEndHere(char next) {
912: return lastChar == '.' || lastChar == '/'
913: || !Character.isJavaIdentifierPart(next);
914: }
915:
916: private void replace(int completeMatch) {
917: int preCount = pendingChars.length()
918: - matchCounts[completeMatch];
919: if (preCount > 0) {
920: buffer.append(pendingChars.substring(0, preCount));
921: }
922: buffer.append(replaceWith[completeMatch]);
923: for (int i = 0; i < matchCounts.length; i++) {
924: matchCounts[i] = 0;
925: }
926: pendingChars.delete(0, pendingChars.length());
927: anyChange = true;
928: }
929:
930: private void writePendingChars() {
931: if (pendingChars.length() > 0) {
932: buffer.append(pendingChars.toString());
933: pendingChars.delete(0, pendingChars.length());
934: }
935: }
936: }
937: }
|