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:
042: package org.netbeans.modules.refactoring.spi;
043:
044: import java.io.File;
045: import java.io.FileInputStream;
046: import java.io.FileOutputStream;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.OutputStream;
050: import java.net.URI;
051: import java.net.URISyntaxException;
052: import java.util.ArrayList;
053: import java.util.Collection;
054: import java.util.HashMap;
055: import org.openide.filesystems.FileObject;
056: import org.openide.filesystems.FileUtil;
057: import org.openide.util.Lookup;
058:
059: /**
060: * Simple backup facility
061: * can be used to backup files and implement undo
062: * For instance Java Refactoring module implements undo this way:
063: *
064: * public Problem prepare(RefactoringElementsBag elements) {
065: * .
066: * .
067: * elements.registerTransaction(new RetoucheCommit(results));
068: * }
069: *
070: * where RetoucheCommit is Transaction:
071: * <pre>
072: * BackupFacility.Handle handle;
073: * public void commit() {
074: * FileObject[] files;
075: * .
076: * .
077: * handle = BackupFacility.getDefault().backup(files);
078: * doCommit();
079: * }
080: * public void rollback() {
081: * //rollback all files
082: * handle.restore();
083: * }
084: * </pre>
085: *
086: * You can register your own implementation via META-INF services.
087: * @see Transaction
088: * @see RefactoringElementImplementation#performChange
089: * @see RefactoringElementImplementation#undoChange
090: * @see RefactoringElementsBag#registerTransaction
091: * @see RefactoringElementsBag#registerFileChange
092: * @see BackupFacility.Handle
093: * @author Jan Becicka
094: */
095: public abstract class BackupFacility {
096:
097: private BackupFacility() {
098: }
099:
100: private static BackupFacility defaultInstance;
101:
102: /**
103: * does beckup
104: * @param file file(s) to backup
105: * @return handle which can be used to restore files
106: * @throws java.io.IOException if backup failed
107: */
108: public abstract Handle backup(FileObject... file)
109: throws IOException;
110:
111: /**
112: * does backup
113: * @param fileObjects FileObjects to backup
114: * @return handle which can be used to restore files
115: * @throws java.io.IOException
116: */
117: public final Handle backup(
118: Collection<? extends FileObject> fileObjects)
119: throws IOException {
120: return backup(fileObjects.toArray(new FileObject[fileObjects
121: .size()]));
122: }
123:
124: /**
125: * do cleanup
126: * all backup files are deleted
127: * all internal structures cleared
128: * default implementa
129: */
130: public abstract void clear();
131:
132: /**
133: * @return default instance of this class. If there is instance of this
134: * class in META-INF services -> this class is returned. Otherwise default
135: * implementation is used.
136: */
137: public static BackupFacility getDefault() {
138: BackupFacility instance = Lookup.getDefault().lookup(
139: BackupFacility.class);
140: return (instance != null) ? instance : getDefaultInstance();
141: }
142:
143: private static synchronized BackupFacility getDefaultInstance() {
144: if (defaultInstance == null) {
145: defaultInstance = new DefaultImpl();
146: }
147:
148: return defaultInstance;
149: }
150:
151: /**
152: * Handle class representing handle to file{s), which were backuped
153: * by
154: * {@link org.netbeans.modules.refactoring.spi.BackupFacility.backup()}
155: */
156: public interface Handle {
157: /**
158: * restore file(s), which was stored by {@link org.netbeans.modules.refactoring.spi.BackupFacility.backup()}
159: * @throws java.io.IOException if restore failed.
160: */
161: void restore() throws IOException;
162: }
163:
164: private static class DefaultHandle implements Handle {
165: ArrayList<Long> handle;
166: DefaultImpl instance;
167:
168: private DefaultHandle(DefaultImpl instance,
169: ArrayList<Long> handles) {
170: this .handle = handles;
171: this .instance = instance;
172: }
173:
174: public void restore() throws IOException {
175: for (long l : handle) {
176: instance.restore(l);
177: }
178: }
179: }
180:
181: private static class DefaultImpl extends BackupFacility {
182:
183: private long currentId = 0;
184: private HashMap<Long, BackupEntry> map = new HashMap();
185:
186: private class BackupEntry {
187: private File file;
188: private URI path;
189: }
190:
191: /** Creates a new instance of BackupFacility */
192: private DefaultImpl() {
193: }
194:
195: public Handle backup(FileObject... file) throws IOException {
196: ArrayList<Long> list = new ArrayList();
197: for (FileObject f : file) {
198: list.add(backup(f));
199: }
200: return new DefaultHandle(this , list);
201: }
202:
203: /**
204: * does beckup
205: * @param file to backup
206: * @return id of backup file
207: * @throws java.io.IOException if backup failed
208: */
209: public long backup(FileObject file) throws IOException {
210: try {
211: BackupEntry entry = new BackupEntry();
212: entry.file = File.createTempFile("nbbackup", null); //NOI18N
213: copy(FileUtil.toFile(file), entry.file);
214: entry.path = file.getURL().toURI();
215: map.put(currentId, entry);
216: entry.file.deleteOnExit();
217: return currentId++;
218: } catch (URISyntaxException ex) {
219: throw (IOException) new IOException(file.toString())
220: .initCause(ex);
221: }
222: }
223:
224: /**
225: * restore file, which was stored by backup(file)
226: * @param id identification of backup transaction
227: * @throws java.io.IOException if restore failed.
228: */
229: void restore(long id) throws IOException {
230: BackupEntry entry = map.get(id);
231: if (entry == null) {
232: throw new IllegalArgumentException("Backup with id "
233: + id + "does not exist"); // NOI18N
234: }
235: File backup = File.createTempFile("nbbackup", null); //NOI18N
236: backup.deleteOnExit();
237: File f = new File(entry.path);
238: if (createNewFile(f)) {
239: backup.createNewFile();
240: copy(f, backup);
241: }
242: FileObject fileObj = FileUtil.toFileObject(f);
243: copy(entry.file, fileObj);
244: entry.file.delete();
245: if (backup.exists()) {
246: entry.file = backup;
247: } else {
248: map.remove(id);
249: }
250: }
251:
252: /**
253: * workaround for #93390
254: */
255: private boolean createNewFile(File f) throws IOException {
256: if (f.exists())
257: return true;
258: File parent = f.getParentFile();
259: if (parent != null) {
260: createNewFolder(parent);
261: }
262: FileUtil.createData(f);
263: return false;
264: }
265:
266: private void createNewFolder(File f) throws IOException {
267: if (!f.exists()) {
268: File parent = f.getParentFile();
269: if (parent != null) {
270: createNewFolder(parent);
271: }
272: FileUtil.createFolder(f);
273: }
274: }
275:
276: private void copy(File a, File b) throws IOException {
277: FileInputStream fs = new FileInputStream(a);
278: FileOutputStream fo = new FileOutputStream(b);
279: copy(fs, fo);
280: }
281:
282: private void copy(File a, FileObject b) throws IOException {
283: FileInputStream fs = new FileInputStream(a);
284: OutputStream fo = b.getOutputStream();
285: copy(fs, fo);
286: }
287:
288: private void copy(InputStream is, OutputStream os)
289: throws IOException {
290: try {
291: FileUtil.copy(is, os);
292: } finally {
293: is.close();
294: os.close();
295: }
296: }
297:
298: public void clear() {
299: for (BackupEntry entry : map.values()) {
300: entry.file.delete();
301: }
302: map.clear();
303: }
304: }
305: }
|