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 General
007: * Public License Version 2 only ("GPL") or the Common Development and Distribution
008: * License("CDDL") (collectively, the "License"). You may not use this file except in
009: * compliance with the License. You can obtain a copy of the License at
010: * http://www.netbeans.org/cddl-gplv2.html or nbbuild/licenses/CDDL-GPL-2-CP. See the
011: * License for the specific language governing permissions and limitations under the
012: * License. When distributing the software, include this License Header Notice in
013: * each file and include the License file at nbbuild/licenses/CDDL-GPL-2-CP. Sun
014: * designates this particular file as subject to the "Classpath" exception as
015: * provided by Sun in the GPL Version 2 section of the License file that
016: * accompanied this code. If applicable, add the following below the License Header,
017: * with the fields enclosed by brackets [] replaced by your own identifying
018: * information: "Portions Copyrighted [year] [name of copyright owner]"
019: *
020: * Contributor(s):
021: *
022: * The Original Software is NetBeans. The Initial Developer of the Original Software
023: * is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun Microsystems, Inc. All
024: * Rights Reserved.
025: *
026: * If you wish your version of this file to be governed by only the CDDL or only the
027: * GPL Version 2, indicate your decision by adding "[Contributor] elects to include
028: * this software in this distribution under the [CDDL or GPL Version 2] license." If
029: * you do not indicate a single choice of license, a recipient has the option to
030: * distribute your version of this file under either the CDDL, the GPL Version 2 or
031: * to extend the choice of license to its licensees as provided above. However, if
032: * you add GPL Version 2 code and therefore, elected the GPL Version 2 license, then
033: * the option applies only if the new code is made subject to such option by the
034: * copyright holder.
035: */
036:
037: package org.netbeans.installer.utils.helper;
038:
039: import java.io.BufferedReader;
040: import java.io.BufferedWriter;
041: import java.io.File;
042: import java.io.FileInputStream;
043: import java.io.FileOutputStream;
044: import java.io.FileReader;
045: import java.io.IOException;
046: import java.io.InputStream;
047: import java.io.InputStreamReader;
048: import java.io.OutputStream;
049: import java.io.OutputStreamWriter;
050: import java.io.PrintWriter;
051: import java.io.UnsupportedEncodingException;
052: import java.io.Writer;
053: import java.util.ArrayList;
054: import java.util.ConcurrentModificationException;
055: import java.util.Iterator;
056: import java.util.List;
057: import java.util.zip.GZIPInputStream;
058: import java.util.zip.GZIPOutputStream;
059: import javax.xml.parsers.ParserConfigurationException;
060: import javax.xml.parsers.SAXParser;
061: import javax.xml.parsers.SAXParserFactory;
062: import org.netbeans.installer.utils.ErrorManager;
063: import org.netbeans.installer.utils.FileUtils;
064: import org.netbeans.installer.utils.StringUtils;
065: import org.netbeans.installer.utils.exceptions.XMLException;
066: import org.xml.sax.Attributes;
067: import org.xml.sax.InputSource;
068: import org.xml.sax.SAXException;
069: import org.xml.sax.helpers.DefaultHandler;
070:
071: /**
072: *
073: * @author Kirill Sorokin
074: */
075: public class FilesList implements Iterable<FileEntry> {
076: /////////////////////////////////////////////////////////////////////////////////
077: // Instance
078: private File listFile;
079: private File tempFile;
080:
081: private List<FileEntry> entries;
082:
083: private int size;
084:
085: // constructors /////////////////////////////////////////////////////////////////
086: public FilesList() {
087: entries = new ArrayList<FileEntry>(CACHE_SIZE);
088: }
089:
090: public FilesList(final File xml) throws IOException, XMLException {
091: this ();
092:
093: loadXml(xml);
094: }
095:
096: // add/remove ///////////////////////////////////////////////////////////////////
097: public void add(final File file) throws IOException {
098: add(new FileEntry(file));
099: }
100:
101: public void add(final FileEntry entry) throws IOException {
102: final String name = entry.getName();
103:
104: int index = 0;
105: while (index < entries.size()) {
106: final String current = entries.get(index).getName();
107:
108: if (current.length() < name.length()) {
109: break;
110: } else if (current.equals(name)) {
111: return;
112: }
113:
114: index++;
115: }
116:
117: entries.add(index, entry);
118: size++;
119:
120: if (entries.size() == CACHE_SIZE) {
121: save();
122: }
123: }
124:
125: public void add(final List<File> list) throws IOException {
126: for (File file : list) {
127: add(file);
128: }
129: }
130:
131: public void add(final FilesList list) throws IOException {
132: for (FileEntry entry : list) {
133: add(entry);
134: }
135: }
136:
137: public void clear() throws IOException {
138: if (listFile != null) {
139: FileUtils.deleteFiles(listFile, tempFile);
140: }
141: entries.clear();
142: size = 0;
143: }
144:
145: // getters //////////////////////////////////////////////////////////////////////
146: public int getSize() {
147: return size;
148: }
149:
150: // list <-> xml /////////////////////////////////////////////////////////////////
151: public FilesList loadXml(final File xml) throws XMLException {
152: return loadXml(xml, null);
153: }
154:
155: public FilesList loadXml(final File xml, final File root)
156: throws XMLException {
157: try {
158: InputStream in = new FileInputStream(xml);
159:
160: loadXml(in, root);
161: in.close();
162:
163: return this ;
164: } catch (IOException e) {
165: throw new XMLException("Cannot parse xml file", e);
166: }
167: }
168:
169: public FilesList loadXmlGz(final File xml) throws XMLException {
170: return loadXmlGz(xml, null);
171: }
172:
173: public FilesList loadXmlGz(final File xml, final File root)
174: throws XMLException {
175: try {
176: InputStream in = new GZIPInputStream(new FileInputStream(
177: xml));
178:
179: loadXml(in, root);
180: in.close();
181:
182: return this ;
183: } catch (IOException e) {
184: throw new XMLException("Cannot parse xml file", e);
185: }
186: }
187:
188: public void saveXml(final File xml) throws XMLException {
189: try {
190: OutputStream out = new FileOutputStream(xml);
191:
192: saveXml(out);
193:
194: out.close();
195: } catch (UnsupportedEncodingException e) {
196: throw new XMLException("Cannot save XML", e);
197: } catch (IOException e) {
198: throw new XMLException("Cannot save XML", e);
199: }
200: }
201:
202: public void saveXmlGz(final File xml) throws XMLException {
203: try {
204: OutputStream out = new GZIPOutputStream(
205: new FileOutputStream(xml));
206:
207: saveXml(out);
208:
209: out.close();
210: } catch (UnsupportedEncodingException e) {
211: throw new XMLException("Cannot save XML", e);
212: } catch (IOException e) {
213: throw new XMLException("Cannot save XML", e);
214: }
215: }
216:
217: // list <-> list :) /////////////////////////////////////////////////////////////
218: public List<File> toList() {
219: final List<File> files = new ArrayList<File>(size);
220:
221: for (FileEntry entry : this ) {
222: files.add(entry.getFile());
223: }
224:
225: return files;
226: }
227:
228: // iterable /////////////////////////////////////////////////////////////////////
229: public Iterator<FileEntry> iterator() {
230: return new FilesListIterator();
231: }
232:
233: // private //////////////////////////////////////////////////////////////////////
234: private void save() throws IOException {
235: if (entries.size() > 0) {
236: if (listFile == null) {
237: listFile = FileUtils.createTempFile();
238: tempFile = FileUtils.createTempFile();
239: }
240:
241: final BufferedReader reader;
242: if (listFile.length() > 0) {
243: reader = new BufferedReader(new InputStreamReader(
244: new GZIPInputStream(new FileInputStream(
245: listFile))));
246: } else {
247: reader = new BufferedReader(new FileReader(listFile));
248: }
249: final BufferedWriter writer = new BufferedWriter(
250: new OutputStreamWriter(new GZIPOutputStream(
251: new FileOutputStream(tempFile))));
252:
253: int index = 0;
254: FileEntry saved = readEntry(reader);
255:
256: while ((index < entries.size()) && (saved != null)) {
257: final String unsavedName = entries.get(index).getName();
258: final String savedName = saved.getName();
259:
260: if (savedName.equals(unsavedName)) {
261: if ((index < entries.size() - 1)
262: && entries.get(index + 1).getName().equals(
263: unsavedName)) {
264: index++;
265: } else {
266: saved = readEntry(reader);
267: }
268: size--;
269: } else {
270: if (unsavedName.length() < savedName.length()) {
271: writeEntry(saved, writer);
272: saved = readEntry(reader);
273: } else {
274: writeEntry(entries.get(index), writer);
275: index++;
276: }
277: }
278: }
279:
280: while (index < entries.size()) {
281: writeEntry(entries.get(index), writer);
282: index++;
283: }
284:
285: while (saved != null) {
286: writeEntry(saved, writer);
287: saved = readEntry(reader);
288: }
289:
290: reader.close();
291:
292: writer.flush();
293: writer.close();
294:
295: FileUtils.copyFile(tempFile, listFile);
296:
297: entries.clear();
298: }
299: }
300:
301: private FileEntry readEntry(final BufferedReader reader)
302: throws IOException {
303: final String name = reader.readLine();
304:
305: if (name != null) {
306: final File file = new File(name);
307: final boolean directory = Boolean.parseBoolean(reader
308: .readLine());
309:
310: if (directory) {
311: final boolean empty = Boolean.parseBoolean(reader
312: .readLine());
313: final long modified = Long.parseLong(reader.readLine());
314: final int permissions = Integer.parseInt(reader
315: .readLine(), 8);
316:
317: return new FileEntry(file, empty, modified, permissions);
318: } else {
319: final long size = Long.parseLong(reader.readLine());
320: final String md5 = reader.readLine();
321: final boolean jarFile = Boolean.parseBoolean(reader
322: .readLine());
323: final boolean packed = Boolean.parseBoolean(reader
324: .readLine());
325: final boolean signed = Boolean.parseBoolean(reader
326: .readLine());
327: final long modified = Long.parseLong(reader.readLine());
328: final int permissions = Integer.parseInt(reader
329: .readLine(), 8);
330:
331: return new FileEntry(file, size, md5, jarFile, packed,
332: signed, modified, permissions);
333: }
334: }
335:
336: return null;
337: }
338:
339: private void writeEntry(final FileEntry entry, final Writer writer)
340: throws IOException {
341: if (entry.getFile().exists() && !entry.isMetaDataReady()) {
342: entry.calculateMetaData();
343: }
344:
345: writer.write(entry.toString());
346: }
347:
348: private void saveXml(final OutputStream out) throws IOException {
349: final PrintWriter writer = new PrintWriter(
350: new OutputStreamWriter(out, ENCODING));
351:
352: writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
353: writer.println("<files-list>");
354:
355: for (FileEntry entry : this ) {
356: if (entry.getFile().exists() && !entry.isMetaDataReady()) {
357: entry.calculateMetaData();
358: }
359:
360: writer.println(" " + entry.toXml());
361: }
362:
363: writer.println("</files-list>");
364:
365: writer.flush();
366: }
367:
368: private void loadXml(final InputStream in, final File root)
369: throws IOException, XMLException {
370: final SAXParserFactory factory = SAXParserFactory.newInstance();
371: final SAXParser parser;
372:
373: try {
374: parser = factory.newSAXParser();
375:
376: parser.parse(new InputSource(in),
377: new FilesListHandler(root));
378: } catch (SAXException e) {
379: throw new XMLException("Cannot load files list from xml", e);
380: } catch (ParserConfigurationException e) {
381: throw new XMLException("Cannot load files list from xml", e);
382: }
383: }
384:
385: /////////////////////////////////////////////////////////////////////////////////
386: // Inner Classes
387: private class FilesListHandler extends DefaultHandler {
388: private boolean entryElement;
389:
390: private File root;
391:
392: private String name;
393:
394: private boolean directory;
395: private boolean empty;
396:
397: private long size;
398: private String md5;
399: private boolean jarFile;
400: private boolean packed;
401: private boolean signed;
402: private long modified;
403: private int permissions;
404:
405: public FilesListHandler(File root) {
406: this .root = root;
407: }
408:
409: public void startElement(final String uri,
410: final String localName, final String qName,
411: final Attributes attributes) throws SAXException {
412: if (qName.equals("entry")) {
413: entryElement = true;
414:
415: String type = attributes.getValue("type");
416: if (type.equals("file")) {
417: directory = false;
418:
419: size = Long.parseLong(attributes.getValue("size"));
420: md5 = attributes.getValue("md5");
421: jarFile = Boolean.parseBoolean(attributes
422: .getValue("jar"));
423:
424: if (jarFile) {
425: packed = Boolean.parseBoolean(attributes
426: .getValue("packed"));
427: signed = Boolean.parseBoolean(attributes
428: .getValue("signed"));
429: } else {
430: packed = false;
431: signed = false;
432: }
433:
434: modified = Long.parseLong(attributes
435: .getValue("modified"));
436: permissions = Integer.parseInt(attributes
437: .getValue("permissions"), 8);
438: } else {
439: directory = true;
440: empty = Boolean.parseBoolean(attributes
441: .getValue("empty"));
442: modified = Long.parseLong(attributes
443: .getValue("modified"));
444: permissions = Integer.parseInt(attributes
445: .getValue("permissions"), 8);
446: }
447: } else {
448: entryElement = false;
449: }
450: }
451:
452: public void characters(final char[] characters,
453: final int start, final int length) throws SAXException {
454: if (entryElement) {
455: final String value = new String(characters, start,
456: length);
457:
458: if (name == null) {
459: name = value;
460: } else {
461: name += value;
462: }
463: }
464: }
465:
466: public void endElement(final String uri,
467: final String localName, final String qName)
468: throws SAXException {
469: if (entryElement) {
470: final File file;
471: if (root == null) {
472: file = new File(name);
473: } else {
474: file = new File(root, name);
475: }
476:
477: name = null;
478:
479: FileEntry entry;
480:
481: if (directory) {
482: entry = new FileEntry(file, empty, modified,
483: permissions);
484: } else {
485: entry = new FileEntry(file, size, md5, jarFile,
486: packed, signed, modified, permissions);
487: }
488:
489: entryElement = false;
490:
491: try {
492: FilesList.this .add(entry);
493: } catch (IOException e) {
494: throw new SAXException("Could not add an entry", e);
495: }
496: }
497: }
498: }
499:
500: private class FilesListIterator implements Iterator<FileEntry> {
501: private int sizeAtConstruction;
502: private boolean listInMemory;
503:
504: private int index;
505: private BufferedReader reader;
506:
507: private FileEntry next;
508:
509: public FilesListIterator() {
510: // if the list size is already bigger than can be reposited in memory -
511: // make sure that all entries are present in the cache file; and set the
512: // iteration mode (over memory or over cache file)
513: if (FilesList.this .listFile != null) {
514: try {
515: FilesList.this .save();
516: } catch (IOException e) {
517: ErrorManager.notifyError("Cannot save list", e);
518: }
519:
520: listInMemory = false;
521: try {
522: reader = new BufferedReader(new InputStreamReader(
523: new GZIPInputStream(new FileInputStream(
524: FilesList.this .listFile))));
525:
526: } catch (IOException e) {
527: ErrorManager.notifyError(
528: "Cannot open reader to the list file", e);
529: }
530: } else {
531: listInMemory = true;
532: index = 0;
533: }
534:
535: sizeAtConstruction = FilesList.this .size;
536: }
537:
538: public boolean hasNext() {
539: if (sizeAtConstruction != FilesList.this .size) {
540: throw new ConcurrentModificationException(
541: "The list was changed, while iterating");
542: }
543:
544: if (next == null) {
545: next = next();
546: }
547:
548: return next != null;
549: }
550:
551: public FileEntry next() {
552: if (next != null) {
553: final FileEntry temp = next;
554: next = null;
555:
556: return temp;
557: } else {
558: FileEntry entry = null;
559:
560: if (listInMemory) {
561: if (index < FilesList.this .entries.size()) {
562: entry = FilesList.this .entries.get(index++);
563: }
564: } else {
565: try {
566: entry = FilesList.this .readEntry(reader);
567:
568: if (entry == null) {
569: reader.close();
570: }
571: } catch (IOException e) {
572: ErrorManager.notifyError(
573: "Cannot read next entry", e);
574: }
575: }
576:
577: return entry;
578: }
579: }
580:
581: public void remove() {
582: throw new UnsupportedOperationException(
583: "Remove is not supported for files list");
584: }
585: }
586:
587: /////////////////////////////////////////////////////////////////////////////////
588: // Constants
589: public static final int CACHE_SIZE = 2500;
590:
591: public static final String ENCODING = StringUtils.ENCODING_UTF8; // NOI18N
592: }
|