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-2006 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:
042: package org.netbeans.napi.gsfret.source;
043:
044: import java.io.*;
045: import java.nio.charset.Charset;
046: import java.util.*;
047: import javax.swing.text.BadLocationException;
048: import javax.swing.text.Document;
049: import org.netbeans.api.queries.FileEncodingQuery;
050: import org.netbeans.editor.BaseDocument;
051: import org.openide.cookies.EditorCookie;
052: import org.openide.filesystems.FileObject;
053: import org.openide.filesystems.FileUtil;
054: import org.openide.loaders.DataObject;
055: import org.openide.text.PositionRef;
056:
057: /**
058: * This file is originally from Retouche, the Java Support
059: * infrastructure in NetBeans. I have modified the file as little
060: * as possible to make merging Retouche fixes back as simple as
061: * possible.
062: *
063: * Class that collects changes built during a modification task run.
064: *
065: * @author Dusan Balek
066: */
067: public final class ModificationResult {
068:
069: private Source js;
070: Map<FileObject, List<Difference>> diffs = new HashMap<FileObject, List<Difference>>();
071:
072: /** Creates a new instance of ModificationResult */
073: ModificationResult(final Source js) {
074: this .js = js;
075: }
076:
077: // API of the class --------------------------------------------------------
078:
079: public Set<? extends FileObject> getModifiedFileObjects() {
080: return diffs.keySet();
081: }
082:
083: public List<? extends Difference> getDifferences(FileObject fo) {
084: return diffs.get(fo);
085: }
086:
087: /**
088: * Once all of the changes have been collected, this method can be used
089: * to commit the changes to the source files
090: */
091: public void commit() throws IOException {
092: try {
093: for (Map.Entry<FileObject, List<Difference>> me : diffs
094: .entrySet()) {
095: commit(me.getKey(), me.getValue(), null);
096: }
097: } finally {
098: if (this .js != null) {
099: this .js.revalidate();
100: }
101: }
102: }
103:
104: private void commit(final FileObject fo,
105: final List<Difference> differences, Writer out)
106: throws IOException {
107: DataObject dObj = DataObject.find(fo);
108: EditorCookie ec = dObj != null ? dObj
109: .getCookie(org.openide.cookies.EditorCookie.class)
110: : null;
111: // if editor cookie was found and user does not provided his own
112: // writer where he wants to see changes, commit the changes to
113: // found document.
114: if (ec != null && out == null) {
115: Document doc = ec.getDocument();
116: if (doc != null) {
117: if (doc instanceof BaseDocument)
118: ((BaseDocument) doc).atomicLock();
119: try {
120: for (Difference diff : differences) {
121: if (diff.isExcluded())
122: continue;
123: try {
124: switch (diff.getKind()) {
125: case INSERT:
126: doc.insertString(
127: diff.getStartPosition()
128: .getOffset(), diff
129: .getNewText(), null);
130: break;
131: case REMOVE:
132: doc.remove(diff.getStartPosition()
133: .getOffset(), diff
134: .getEndPosition().getOffset()
135: - diff.getStartPosition()
136: .getOffset());
137: break;
138: case CHANGE:
139: doc.remove(diff.getStartPosition()
140: .getOffset(), diff
141: .getEndPosition().getOffset()
142: - diff.getStartPosition()
143: .getOffset());
144: doc.insertString(
145: diff.getStartPosition()
146: .getOffset(), diff
147: .getNewText(), null);
148: break;
149: }
150: } catch (BadLocationException ex) {
151: IOException ioe = new IOException();
152: ioe.initCause(ex);
153: throw ioe;
154: }
155: }
156: } finally {
157: if (doc instanceof BaseDocument)
158: ((BaseDocument) doc).atomicUnlock();
159: }
160: return;
161: }
162: }
163: InputStream ins = null;
164: ByteArrayOutputStream baos = null;
165: Reader in = null;
166: try {
167: Charset encoding = FileEncodingQuery.getEncoding(fo);
168: ins = fo.getInputStream();
169: baos = new ByteArrayOutputStream();
170: FileUtil.copy(ins, baos);
171:
172: ins.close();
173: ins = null;
174: byte[] arr = baos.toByteArray();
175: int arrLength = convertToLF(arr);
176: baos.close();
177: baos = null;
178: in = new InputStreamReader(new ByteArrayInputStream(arr, 0,
179: arrLength), encoding);
180: // initialize standard commit output stream, if user
181: // does not provide his own writer
182: if (out == null) {
183: out = new OutputStreamWriter(fo.getOutputStream(),
184: encoding);
185: }
186: int offset = 0;
187: for (Difference diff : differences) {
188: if (diff.isExcluded())
189: continue;
190: int pos = diff.getStartPosition().getOffset();
191: int toread = pos - offset;
192: char[] buff = new char[toread];
193: int n;
194: int rc = 0;
195: while ((n = in.read(buff, 0, toread - rc)) > 0
196: && rc < toread) {
197: out.write(buff, 0, n);
198: rc += n;
199: offset += n;
200: }
201: switch (diff.getKind()) {
202: case INSERT:
203: out.write(diff.getNewText());
204: break;
205: case REMOVE:
206: int len = diff.getEndPosition().getOffset()
207: - diff.getStartPosition().getOffset();
208: in.skip(len);
209: offset += len;
210: break;
211: case CHANGE:
212: len = diff.getEndPosition().getOffset()
213: - diff.getStartPosition().getOffset();
214: in.skip(len);
215: offset += len;
216: out.write(diff.getNewText());
217: break;
218: }
219: }
220: char[] buff = new char[1024];
221: int n;
222: while ((n = in.read(buff)) > 0)
223: out.write(buff, 0, n);
224: } finally {
225: if (ins != null)
226: ins.close();
227: if (baos != null)
228: baos.close();
229: if (in != null)
230: in.close();
231: if (out != null)
232: out.close();
233: }
234: }
235:
236: private int convertToLF(byte[] buff) {
237: int j = 0;
238: for (int i = 0; i < buff.length; i++) {
239: if (buff[i] != '\r') {
240: buff[j++] = buff[i];
241: }
242: }
243: return j;
244: }
245:
246: /**
247: * Returned string represents preview of resulting source. No difference
248: * really is applied. Respects {@code isExcluded()} flag of difference.
249: *
250: * @param there can be more resulting source, user has to specify
251: * which wants to preview.
252: * @return if changes are applied source looks like return string
253: */
254: public String getResultingSource(FileObject fileObject)
255: throws IOException {
256: assert fileObject != null : "Provided fileObject is null";
257: StringWriter writer = new StringWriter();
258: commit(fileObject, diffs.get(fileObject), writer);
259:
260: return writer.toString();
261: }
262:
263: public static final class Difference {
264: Kind kind;
265: PositionRef startPos;
266: PositionRef endPos;
267: String oldText;
268: String newText;
269: String description;
270: private boolean excluded;
271:
272: public Difference(Kind kind, PositionRef startPos,
273: PositionRef endPos, String oldText, String newText,
274: String description) {
275: this .kind = kind;
276: this .startPos = startPos;
277: this .endPos = endPos;
278: this .oldText = oldText;
279: this .newText = newText;
280: this .description = description;
281: this .excluded = false;
282: }
283:
284: Difference(Kind kind, PositionRef startPos, PositionRef endPos,
285: String oldText, String newText) {
286: this (kind, startPos, endPos, oldText, newText, null);
287: }
288:
289: public Kind getKind() {
290: return kind;
291: }
292:
293: public PositionRef getStartPosition() {
294: return startPos;
295: }
296:
297: public PositionRef getEndPosition() {
298: return endPos;
299: }
300:
301: public String getOldText() {
302: return oldText;
303: }
304:
305: public String getNewText() {
306: return newText;
307: }
308:
309: public boolean isExcluded() {
310: return excluded;
311: }
312:
313: public void exclude(boolean b) {
314: excluded = b;
315: }
316:
317: @Override
318: public String toString() {
319: return kind + "<" + startPos.getOffset() + ", "
320: + endPos.getOffset() + ">: " + oldText + " -> "
321: + newText;
322: }
323:
324: public String getDescription() {
325: return description;
326: }
327:
328: public static enum Kind {
329: INSERT, REMOVE, CHANGE
330: }
331: }
332: }
|