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.api.java.source;
043:
044: import java.io.*;
045: import java.nio.charset.Charset;
046: import java.util.*;
047: import java.util.logging.Level;
048: import java.util.logging.Logger;
049: import javax.swing.text.BadLocationException;
050: import javax.swing.text.Document;
051: import javax.tools.JavaFileObject;
052: import org.netbeans.api.java.source.ModificationResult.CreateChange;
053: import org.netbeans.api.queries.FileEncodingQuery;
054: import org.netbeans.editor.BaseDocument;
055: import org.netbeans.modules.java.source.JavaSourceSupportAccessor;
056: import org.netbeans.modules.java.source.usages.RepositoryUpdater;
057: import org.openide.cookies.EditorCookie;
058: import org.openide.filesystems.FileObject;
059: import org.openide.filesystems.FileUtil;
060: import org.openide.loaders.DataObject;
061: import org.openide.text.PositionRef;
062:
063: /**
064: * Class that collects changes built during a modification task run.
065: *
066: * @author Dusan Balek
067: */
068: public final class ModificationResult {
069:
070: private JavaSource js;
071: Map<FileObject, List<Difference>> diffs = new HashMap<FileObject, List<Difference>>();
072:
073: /** Creates a new instance of ModificationResult */
074: ModificationResult(final JavaSource js) {
075: this .js = js;
076: }
077:
078: // API of the class --------------------------------------------------------
079:
080: public Set<? extends FileObject> getModifiedFileObjects() {
081: return diffs.keySet();
082: }
083:
084: public List<? extends Difference> getDifferences(FileObject fo) {
085: return diffs.get(fo);
086: }
087:
088: public Set<File> getNewFiles() {
089: Set<File> newFiles = new HashSet<File>();
090: for (List<Difference> ds : diffs.values()) {
091: for (Difference d : ds) {
092: if (d.getKind() == Difference.Kind.CREATE) {
093: newFiles.add(new File(((CreateChange) d)
094: .getFileObject().toUri()));
095: }
096: }
097: }
098: return newFiles;
099: }
100:
101: /**
102: * Once all of the changes have been collected, this method can be used
103: * to commit the changes to the source files
104: */
105: public void commit() throws IOException {
106: try {
107: RepositoryUpdater.getDefault().lockRU();
108: for (Map.Entry<FileObject, List<Difference>> me : diffs
109: .entrySet()) {
110: commit(me.getKey(), me.getValue(), null);
111: }
112: } finally {
113: RepositoryUpdater.getDefault().unlockRU();
114: Set<FileObject> alreadyRefreshed = new HashSet<FileObject>();
115: if (this .js != null) {
116: this .js.revalidate();
117: alreadyRefreshed.addAll(js.getFileObjects());
118: }
119: for (FileObject currentlyVisibleInEditor : JavaSourceSupportAccessor.ACCESSOR
120: .getVisibleEditorsFiles()) {
121: if (!alreadyRefreshed
122: .contains(currentlyVisibleInEditor)) {
123: JavaSource source = JavaSource
124: .forFileObject(currentlyVisibleInEditor);
125: if (source != null) {
126: source.revalidate();
127: }
128: }
129: }
130: }
131: }
132:
133: private void commit(final FileObject fo,
134: final List<Difference> differences, Writer out)
135: throws IOException {
136: DataObject dObj = DataObject.find(fo);
137: EditorCookie ec = dObj != null ? dObj
138: .getCookie(org.openide.cookies.EditorCookie.class)
139: : null;
140: // if editor cookie was found and user does not provided his own
141: // writer where he wants to see changes, commit the changes to
142: // found document.
143: if (ec != null && out == null) {
144: Document doc = ec.getDocument();
145: if (doc != null) {
146: if (doc instanceof BaseDocument)
147: ((BaseDocument) doc).atomicLock();
148: boolean success = false;
149: try {
150: for (Difference diff : differences) {
151: if (diff.isExcluded())
152: continue;
153: try {
154: switch (diff.getKind()) {
155: case INSERT:
156: doc.insertString(
157: diff.getStartPosition()
158: .getOffset(), diff
159: .getNewText(), null);
160: break;
161: case REMOVE:
162: doc.remove(diff.getStartPosition()
163: .getOffset(), diff
164: .getEndPosition().getOffset()
165: - diff.getStartPosition()
166: .getOffset());
167: break;
168: case CHANGE:
169: doc.remove(diff.getStartPosition()
170: .getOffset(), diff
171: .getEndPosition().getOffset()
172: - diff.getStartPosition()
173: .getOffset());
174: doc.insertString(
175: diff.getStartPosition()
176: .getOffset(), diff
177: .getNewText(), null);
178: break;
179: case CREATE:
180: createUnit(diff, out);
181: break;
182: }
183: } catch (BadLocationException ex) {
184: IOException ioe = new IOException();
185: ioe.initCause(ex);
186: throw ioe;
187: }
188: }
189: success = true;
190: } finally {
191: if (doc instanceof BaseDocument) {
192: if (!success) {
193: //something went wrong, rollback the changes:
194: ((BaseDocument) doc).atomicUndo();
195: }
196: ((BaseDocument) doc).atomicUnlock();
197: }
198: }
199: return;
200: }
201: }
202: InputStream ins = null;
203: ByteArrayOutputStream baos = null;
204: Reader in = null;
205: try {
206: Charset encoding = FileEncodingQuery.getEncoding(fo);
207: ins = fo.getInputStream();
208: baos = new ByteArrayOutputStream();
209: FileUtil.copy(ins, baos);
210:
211: ins.close();
212: ins = null;
213: byte[] arr = baos.toByteArray();
214: int arrLength = convertToLF(arr);
215: baos.close();
216: baos = null;
217: in = new InputStreamReader(new ByteArrayInputStream(arr, 0,
218: arrLength), encoding);
219: // initialize standard commit output stream, if user
220: // does not provide his own writer
221: boolean ownOutput = out != null;
222: if (out == null) {
223: out = new OutputStreamWriter(fo.getOutputStream(),
224: encoding);
225: }
226: int offset = 0;
227: for (Difference diff : differences) {
228: if (diff.isExcluded())
229: continue;
230: if (Difference.Kind.CREATE == diff.getKind()) {
231: if (!ownOutput) {
232: createUnit(diff, null);
233: }
234: continue;
235: }
236: int pos = diff.getStartPosition().getOffset();
237: int toread = pos - offset;
238: char[] buff = new char[toread];
239: int n;
240: int rc = 0;
241: while ((n = in.read(buff, 0, toread - rc)) > 0
242: && rc < toread) {
243: out.write(buff, 0, n);
244: rc += n;
245: offset += n;
246: }
247: switch (diff.getKind()) {
248: case INSERT:
249: out.write(diff.getNewText());
250: break;
251: case REMOVE:
252: int len = diff.getEndPosition().getOffset()
253: - diff.getStartPosition().getOffset();
254: in.skip(len);
255: offset += len;
256: break;
257: case CHANGE:
258: len = diff.getEndPosition().getOffset()
259: - diff.getStartPosition().getOffset();
260: in.skip(len);
261: offset += len;
262: out.write(diff.getNewText());
263: break;
264: }
265: }
266: char[] buff = new char[1024];
267: int n;
268: while ((n = in.read(buff)) > 0)
269: out.write(buff, 0, n);
270: } finally {
271: if (ins != null)
272: ins.close();
273: if (baos != null)
274: baos.close();
275: if (in != null)
276: in.close();
277: if (out != null)
278: out.close();
279: }
280: }
281:
282: private void createUnit(Difference diff, Writer out) {
283: CreateChange change = (CreateChange) diff;
284: Writer w = out;
285: try {
286: if (w == null) {
287: change.getFileObject().openOutputStream();
288: w = change.getFileObject().openWriter();
289: }
290: w.append(change.getNewText());
291: } catch (IOException e) {
292: Logger.getLogger(WorkingCopy.class.getName()).log(
293: Level.SEVERE, e.getMessage(), e);
294: } finally {
295: if (w != null) {
296: try {
297: w.close();
298: } catch (IOException e) {
299: Logger.getLogger(WorkingCopy.class.getName()).log(
300: Level.SEVERE, e.getMessage(), e);
301: }
302: }
303: }
304: }
305:
306: private int convertToLF(byte[] buff) {
307: int j = 0;
308: for (int i = 0; i < buff.length; i++) {
309: if (buff[i] != '\r') {
310: buff[j++] = buff[i];
311: }
312: }
313: return j;
314: }
315:
316: /**
317: * Returned string represents preview of resulting source. No difference
318: * really is applied. Respects {@code isExcluded()} flag of difference.
319: *
320: * @param there can be more resulting source, user has to specify
321: * which wants to preview.
322: * @return if changes are applied source looks like return string
323: */
324: public String getResultingSource(FileObject fileObject)
325: throws IOException {
326: assert fileObject != null : "Provided fileObject is null";
327: StringWriter writer = new StringWriter();
328: commit(fileObject, diffs.get(fileObject), writer);
329:
330: return writer.toString();
331: }
332:
333: public static class Difference {
334: Kind kind;
335: PositionRef startPos;
336: PositionRef endPos;
337: String oldText;
338: String newText;
339: String description;
340: private boolean excluded;
341:
342: Difference(Kind kind, PositionRef startPos, PositionRef endPos,
343: String oldText, String newText, String description) {
344: this .kind = kind;
345: this .startPos = startPos;
346: this .endPos = endPos;
347: this .oldText = oldText;
348: this .newText = newText;
349: this .description = description;
350: this .excluded = false;
351: }
352:
353: Difference(Kind kind, PositionRef startPos, PositionRef endPos,
354: String oldText, String newText) {
355: this (kind, startPos, endPos, oldText, newText, null);
356: }
357:
358: public Kind getKind() {
359: return kind;
360: }
361:
362: public PositionRef getStartPosition() {
363: return startPos;
364: }
365:
366: public PositionRef getEndPosition() {
367: return endPos;
368: }
369:
370: public String getOldText() {
371: return oldText;
372: }
373:
374: public String getNewText() {
375: return newText;
376: }
377:
378: public boolean isExcluded() {
379: return excluded;
380: }
381:
382: public void exclude(boolean b) {
383: excluded = b;
384: }
385:
386: @Override
387: public String toString() {
388: return kind + "<" + startPos.getOffset() + ", "
389: + endPos.getOffset() + ">: " + oldText + " -> "
390: + newText;
391: }
392:
393: public String getDescription() {
394: return description;
395: }
396:
397: public static enum Kind {
398: INSERT, REMOVE, CHANGE, CREATE;
399: }
400: }
401:
402: static class CreateChange extends Difference {
403: JavaFileObject fileObject;
404:
405: CreateChange(JavaFileObject fileObject, String text) {
406: super (Kind.CREATE, null, null, null, text, "Create file "
407: + fileObject.getName());
408: this .fileObject = fileObject;
409: }
410:
411: public JavaFileObject getFileObject() {
412: return fileObject;
413: }
414:
415: @Override
416: public String toString() {
417: return kind + "Create File: " + fileObject.getName()
418: + "; contents = \"\n" + newText + "\"";
419: }
420: }
421: }
|