PageRenderTime 43ms CodeModel.GetById 1ms app.highlight 36ms RepoModel.GetById 0ms app.codeStats 1ms

/jEdit/tags/jedit-4-5-pre1/installer/TarEntry.java

#
Java | 729 lines | 356 code | 112 blank | 261 comment | 28 complexity | 2c5b9c2b57b2b7b215598707f9bd28b9 MD5 | raw file
  1/*
  2** Authored by Timothy Gerard Endres
  3** <mailto:time@gjt.org>  <http://www.trustice.com>
  4** 
  5** This work has been placed into the public domain.
  6** You may use this work in any way and for any purpose you wish.
  7**
  8** THIS SOFTWARE IS PROVIDED AS-IS WITHOUT WARRANTY OF ANY KIND,
  9** NOT EVEN THE IMPLIED WARRANTY OF MERCHANTABILITY. THE AUTHOR
 10** OF THIS SOFTWARE, ASSUMES _NO_ RESPONSIBILITY FOR ANY
 11** CONSEQUENCE RESULTING FROM THE USE, MODIFICATION, OR
 12** REDISTRIBUTION OF THIS SOFTWARE. 
 13** 
 14*/
 15
 16package installer;
 17
 18import java.io.*;
 19import java.util.Date;
 20
 21
 22/**
 23 *
 24 * This class represents an entry in a Tar archive. It consists
 25 * of the entry's header, as well as the entry's File. Entries
 26 * can be instantiated in one of three ways, depending on how
 27 * they are to be used.
 28 * <p>
 29 * TarEntries that are created from the header bytes read from
 30 * an archive are instantiated with the TarEntry( byte[] )
 31 * constructor. These entries will be used when extracting from
 32 * or listing the contents of an archive. These entries have their
 33 * header filled in using the header bytes. They also set the File
 34 * to null, since they reference an archive entry not a file.
 35 * <p>
 36 * TarEntries that are created from Files that are to be written
 37 * into an archive are instantiated with the TarEntry( File )
 38 * constructor. These entries have their header filled in using
 39 * the File's information. They also keep a reference to the File
 40 * for convenience when writing entries.
 41 * <p>
 42 * Finally, TarEntries can be constructed from nothing but a name.
 43 * This allows the programmer to construct the entry by hand, for
 44 * instance when only an InputStream is available for writing to
 45 * the archive, and the header information is constructed from
 46 * other information. In this case the header fields are set to
 47 * defaults and the File is set to null.
 48 *
 49 * <p>
 50 * The C structure for a Tar Entry's header is:
 51 * <pre>
 52 * struct header {
 53 *		char	name[NAMSIZ];
 54 *		char	mode[8];
 55 *		char	uid[8];
 56 *		char	gid[8];
 57 *		char	size[12];
 58 *		char	mtime[12];
 59 *		char	chksum[8];
 60 *		char	linkflag;
 61 *		char	linkname[NAMSIZ];
 62 *		char	magic[8];
 63 *		char	uname[TUNMLEN];
 64 *		char	gname[TGNMLEN];
 65 *		char	devmajor[8];
 66 *		char	devminor[8];
 67 *	} header;
 68 * </pre>
 69 *
 70 * @see TarHeader
 71 *
 72 */
 73
 74public
 75class		TarEntry
 76extends		Object
 77	{
 78	/**
 79	 * If this entry represents a File, this references it.
 80	 */
 81	protected File				file;
 82
 83	/**
 84	 * This is the entry's header information.
 85	 */
 86	protected TarHeader			header;
 87
 88	/**
 89	 * Construct an entry with only a name. This allows the programmer
 90	 * to construct the entry's header "by hand". File is set to null.
 91	 */
 92	public
 93	TarEntry( String name )
 94		{
 95		this.initialize();
 96		this.nameTarHeader( this.header, name );
 97		}
 98
 99	/**
100	 * Construct an entry for a file. File is set to file, and the
101	 * header is constructed from information from the file.
102	 *
103	 * @param file The file that the entry represents.
104	 */
105	public
106	TarEntry( File file )
107		throws InvalidHeaderException
108		{
109		this.initialize();
110		this.getFileTarHeader( this.header, file );
111		}
112
113	/**
114	 * Construct an entry from an archive's header bytes. File is set
115	 * to null.
116	 *
117	 * @param headerBuf The header bytes from a tar archive entry.
118	 */
119	public
120	TarEntry( byte[] headerBuf )
121		throws InvalidHeaderException
122		{
123		this.initialize();
124		this.parseTarHeader( this.header, headerBuf );
125		}
126
127	/**
128	 * Initialization code common to all constructors.
129	 */
130	private void
131	initialize()
132		{
133		this.file = null;
134		this.header = new TarHeader();
135		}
136
137	/**
138	 * Determine if the two entries are equal. Equality is determined
139	 * by the header names being equal.
140	 *
141	 * @return it Entry to be checked for equality.
142	 * @return True if the entries are equal.
143	 */
144	public boolean
145	equals( TarEntry it )
146		{
147		return
148			this.header.name.toString().equals
149				( it.header.name.toString() );
150		}
151
152	/**
153	 * Determine if the given entry is a descendant of this entry.
154	 * Descendancy is determined by the name of the descendant
155	 * starting with this entry's name.
156	 *
157	 * @param desc Entry to be checked as a descendent of this.
158	 * @return True if entry is a descendant of this.
159	 */
160	public boolean
161	isDescendent( TarEntry desc )
162		{
163		return
164			desc.header.name.toString().startsWith
165				( this.header.name.toString() );
166		}
167
168	/**
169	 * Get this entry's header.
170	 *
171	 * @return This entry's TarHeader.
172	 */
173	public TarHeader
174	getHeader()
175		{
176		return this.header;
177		}
178
179	/**
180	 * Get this entry's name.
181	 *
182	 * @return This entry's name.
183	 */
184	public String
185	getName()
186		{
187		return this.header.name.toString();
188		}
189
190	/**
191	 * Set this entry's name.
192	 *
193	 * @param name This entry's new name.
194	 */
195	public void
196	setName( String name )
197		{
198		this.header.name =
199			new StringBuffer( name );
200		}
201
202	/**
203	 * Get this entry's user id.
204	 *
205	 * @return This entry's user id.
206	 */
207	public int
208	getUserId()
209		{
210		return this.header.userId;
211		}
212
213	/**
214	 * Set this entry's user id.
215	 *
216	 * @param userId This entry's new user id.
217	 */
218	public void
219	setUserId( int userId )
220		{
221		this.header.userId = userId;
222		}
223
224	/**
225	 * Get this entry's group id.
226	 *
227	 * @return This entry's group id.
228	 */
229	public int
230	getGroupId()
231		{
232		return this.header.groupId;
233		}
234
235	/**
236	 * Set this entry's group id.
237	 *
238	 * @param groupId This entry's new group id.
239	 */
240	public void
241	setGroupId( int groupId )
242		{
243		this.header.groupId = groupId;
244		}
245
246	/**
247	 * Get this entry's user name.
248	 *
249	 * @return This entry's user name.
250	 */
251	public String
252	getUserName()
253		{
254		return this.header.userName.toString();
255		}
256
257	/**
258	 * Set this entry's user name.
259	 *
260	 * @param userName This entry's new user name.
261	 */
262	public void
263	setUserName( String userName )
264		{
265		this.header.userName =
266			new StringBuffer( userName );
267		}
268
269	/**
270	 * Get this entry's group name.
271	 *
272	 * @return This entry's group name.
273	 */
274	public String
275	getGroupName()
276		{
277		return this.header.groupName.toString();
278		}
279
280	/**
281	 * Set this entry's group name.
282	 *
283	 * @param groupName This entry's new group name.
284	 */
285	public void
286	setGroupName( String groupName )
287		{
288		this.header.groupName =
289			new StringBuffer( groupName );
290		}
291
292	/**
293	 * Convenience method to set this entry's group and user ids.
294	 *
295	 * @param userId This entry's new user id.
296	 * @param groupId This entry's new group id.
297	 */
298	public void
299	setIds( int userId, int groupId )
300		{
301		this.setUserId( userId );
302		this.setGroupId( groupId );
303		}
304
305	/**
306	 * Convenience method to set this entry's group and user names.
307	 *
308	 * @param userName This entry's new user name.
309	 * @param groupName This entry's new group name.
310	 */
311	public void
312	setNames( String userName, String groupName )
313		{
314		this.setUserName( userName );
315		this.setGroupName( groupName );
316		}
317
318	/**
319	 * Set this entry's modification time. The parameter passed
320	 * to this method is in "Java time".
321	 *
322	 * @param time This entry's new modification time.
323	 */
324	public void
325	setModTime( long time )
326		{
327		this.header.modTime = time / 1000;
328		}
329
330	/**
331	 * Set this entry's modification time.
332	 *
333	 * @param time This entry's new modification time.
334	 */
335	public void
336	setModTime( Date time )
337		{
338		this.header.modTime = time.getTime() / 1000;
339		}
340
341	/**
342	 * Set this entry's modification time.
343	 *
344	 * @param time This entry's new modification time.
345	 */
346	public Date
347	getModTime()
348		{
349		return new Date( this.header.modTime * 1000 );
350		}
351
352	/**
353	 * Get this entry's file.
354	 *
355	 * @return This entry's file.
356	 */
357	public File
358	getFile()
359		{
360		return this.file;
361		}
362
363	/**
364	 * Get this entry's file size.
365	 *
366	 * @return This entry's file size.
367	 */
368	public long
369	getSize()
370		{
371		return this.header.size;
372		}
373
374	/**
375	 * Set this entry's file size.
376	 *
377	 * @param size This entry's new file size.
378	 */
379	public void
380	setSize( long size )
381		{
382		this.header.size = size;
383		}
384
385	/**
386	 * Convenience method that will modify an entry's name directly
387	 * in place in an entry header buffer byte array.
388	 *
389	 * @param outbuf The buffer containing the entry header to modify.
390	 * @param newName The new name to place into the header buffer.
391	 */
392	public void
393	adjustEntryName( byte[] outbuf, String newName )
394		{
395		int offset = 0;
396		offset = TarHeader.getNameBytes
397			( new StringBuffer( newName ),
398				outbuf, offset, TarHeader.NAMELEN );
399		}
400
401	/**
402	 * Return whether or not this entry represents a directory.
403	 *
404	 * @return True if this entry is a directory.
405	 */
406	public boolean
407	isDirectory()
408		{
409		if ( this.file != null )
410			return this.file.isDirectory();
411
412		if ( this.header != null )
413			{
414			if ( this.header.linkFlag == TarHeader.LF_DIR )
415				return true;
416
417			if ( this.header.name.toString().endsWith( "/" ) )
418				return true;
419			}
420
421		return false;
422		}
423
424	/**
425	 * Fill in a TarHeader with information from a File.
426	 *
427	 * @param hdr The TarHeader to fill in.
428	 * @param file The file from which to get the header information.
429	 */
430	public void
431	getFileTarHeader( TarHeader hdr, File file )
432		throws InvalidHeaderException
433		{
434		this.file = file;
435
436		String name = file.getPath();
437		String osname = System.getProperty( "os.name" );
438		if ( osname != null )
439			{
440			// Strip off drive letters!
441			// REVIEW Would a better check be "(File.separator == '\')"?
442
443			// String Win32Prefix = "Windows";
444			// String prefix = osname.substring( 0, Win32Prefix.length() );
445			// if ( prefix.equalsIgnoreCase( Win32Prefix ) )
446
447			// if ( File.separatorChar == '\\' )
448
449			// Per Patrick Beard:
450			String Win32Prefix = "windows";
451			if ( osname.toLowerCase().startsWith( Win32Prefix ) )
452				{
453				if ( name.length() > 2 )
454					{
455					char ch1 = name.charAt(0);
456					char ch2 = name.charAt(1);
457					if ( ch2 == ':'
458						&& ( (ch1 >= 'a' && ch1 <= 'z')
459							|| (ch1 >= 'A' && ch1 <= 'Z') ) )
460						{
461						name = name.substring( 2 );
462						}
463					}
464				}
465			}
466
467		name = name.replace( File.separatorChar, '/' );
468
469		// No absolute pathnames
470		// Windows (and Posix?) paths can start with "\\NetworkDrive\",
471		// so we loop on starting /'s.
472		
473		for ( ; name.startsWith( "/" ) ; )
474			name = name.substring( 1 );
475
476 		hdr.linkName = new StringBuffer( "" );
477
478		hdr.name = new StringBuffer( name );
479
480		if ( file.isDirectory() )
481			{
482			hdr.mode = 040755;
483			hdr.linkFlag = TarHeader.LF_DIR;
484			if ( hdr.name.charAt( hdr.name.length() - 1 ) != '/' )
485				hdr.name.append( "/" );
486			}
487		else
488			{
489			hdr.mode = 0100644;
490			hdr.linkFlag = TarHeader.LF_NORMAL;
491			}
492
493		// UNDONE When File lets us get the userName, use it!
494
495		hdr.size = file.length();
496		hdr.modTime = file.lastModified() / 1000;
497		hdr.checkSum = 0;
498		hdr.devMajor = 0;
499		hdr.devMinor = 0;
500		}
501
502	/**
503	 * If this entry represents a file, and the file is a directory, return
504	 * an array of TarEntries for this entry's children.
505	 *
506	 * @return An array of TarEntry's for this entry's children.
507	 */
508	public TarEntry[]
509	getDirectoryEntries()
510		throws InvalidHeaderException
511		{
512		if ( this.file == null
513				|| ! this.file.isDirectory() )
514			{
515			return new TarEntry[0];
516			}
517
518		String[] list = this.file.list();
519
520		TarEntry[] result = new TarEntry[ list.length ];
521
522		for ( int i = 0 ; i < list.length ; ++i )
523			{
524			result[i] =
525				new TarEntry
526					( new File( this.file, list[i] ) );
527			}
528
529		return result;
530		}
531
532	/**
533	 * Compute the checksum of a tar entry header.
534	 *
535	 * @param buf The tar entry's header buffer.
536	 * @return The computed checksum.
537	 */
538	public long
539	computeCheckSum( byte[] buf )
540		{
541		long sum = 0;
542
543		for ( int i = 0 ; i < buf.length ; ++i )
544			{
545			sum += 255 & buf[ i ];
546			}
547
548		return sum;
549		}
550
551	/**
552	 * Write an entry's header information to a header buffer.
553	 *
554	 * @param outbuf The tar entry header buffer to fill in.
555	 */
556	public void
557	writeEntryHeader( byte[] outbuf )
558		{
559		int offset = 0;
560
561		offset = TarHeader.getNameBytes
562			( this.header.name, outbuf, offset, TarHeader.NAMELEN );
563
564		offset = TarHeader.getOctalBytes
565			( this.header.mode, outbuf, offset, TarHeader.MODELEN );
566
567		offset = TarHeader.getOctalBytes
568			( this.header.userId, outbuf, offset, TarHeader.UIDLEN );
569
570		offset = TarHeader.getOctalBytes
571			( this.header.groupId, outbuf, offset, TarHeader.GIDLEN );
572
573		long size = this.header.size;
574
575		offset = TarHeader.getLongOctalBytes
576			( size, outbuf, offset, TarHeader.SIZELEN );
577
578		offset = TarHeader.getLongOctalBytes
579			( this.header.modTime, outbuf, offset, TarHeader.MODTIMELEN );
580
581		int csOffset = offset;
582		for ( int c = 0 ; c < TarHeader.CHKSUMLEN ; ++c )
583			outbuf[ offset++ ] = (byte) ' ';
584
585		outbuf[ offset++ ] = this.header.linkFlag;
586
587		offset = TarHeader.getNameBytes
588			( this.header.linkName, outbuf, offset, TarHeader.NAMELEN );
589
590		offset = TarHeader.getNameBytes
591			( this.header.magic, outbuf, offset, TarHeader.MAGICLEN );
592
593		offset = TarHeader.getNameBytes
594			( this.header.userName, outbuf, offset, TarHeader.UNAMELEN );
595
596		offset = TarHeader.getNameBytes
597			( this.header.groupName, outbuf, offset, TarHeader.GNAMELEN );
598
599		offset = TarHeader.getOctalBytes
600			( this.header.devMajor, outbuf, offset, TarHeader.DEVLEN );
601
602		offset = TarHeader.getOctalBytes
603			( this.header.devMinor, outbuf, offset, TarHeader.DEVLEN );
604
605		for ( ; offset < outbuf.length ; )
606			outbuf[ offset++ ] = 0;
607
608		long checkSum = this.computeCheckSum( outbuf );
609
610		TarHeader.getCheckSumOctalBytes
611			( checkSum, outbuf, csOffset, TarHeader.CHKSUMLEN );
612		}
613
614	/**
615	 * Parse an entry's TarHeader information from a header buffer.
616	 *
617	 * @param hdr The TarHeader to fill in from the buffer information.
618	 * @param header The tar entry header buffer to get information from.
619	 */
620	public void
621	parseTarHeader( TarHeader hdr, byte[] header )
622		throws InvalidHeaderException
623		{
624		int offset = 0;
625
626		hdr.name =
627			TarHeader.parseName( header, offset, TarHeader.NAMELEN );
628
629		offset += TarHeader.NAMELEN;
630
631		hdr.mode = (int)
632			TarHeader.parseOctal( header, offset, TarHeader.MODELEN );
633
634		offset += TarHeader.MODELEN;
635
636		hdr.userId = (int)
637			TarHeader.parseOctal( header, offset, TarHeader.UIDLEN );
638
639		offset += TarHeader.UIDLEN;
640
641		hdr.groupId = (int)
642			TarHeader.parseOctal( header, offset, TarHeader.GIDLEN );
643
644		offset += TarHeader.GIDLEN;
645
646		hdr.size =
647			TarHeader.parseOctal( header, offset, TarHeader.SIZELEN );
648
649		offset += TarHeader.SIZELEN;
650
651		hdr.modTime =
652			TarHeader.parseOctal( header, offset, TarHeader.MODTIMELEN );
653
654		offset += TarHeader.MODTIMELEN;
655
656		hdr.checkSum = (int)
657			TarHeader.parseOctal( header, offset, TarHeader.CHKSUMLEN );
658
659		offset += TarHeader.CHKSUMLEN;
660
661		hdr.linkFlag = header[ offset++ ];
662
663		hdr.linkName =
664			TarHeader.parseName( header, offset, TarHeader.NAMELEN );
665
666		offset += TarHeader.NAMELEN;
667
668		hdr.magic =
669			TarHeader.parseName( header, offset, TarHeader.MAGICLEN );
670
671		offset += TarHeader.MAGICLEN;
672
673		hdr.userName =
674			TarHeader.parseName( header, offset, TarHeader.UNAMELEN );
675
676		offset += TarHeader.UNAMELEN;
677
678		hdr.groupName =
679			TarHeader.parseName( header, offset, TarHeader.GNAMELEN );
680
681		offset += TarHeader.GNAMELEN;
682
683		hdr.devMajor = (int)
684			TarHeader.parseOctal( header, offset, TarHeader.DEVLEN );
685
686		offset += TarHeader.DEVLEN;
687
688		hdr.devMinor = (int)
689			TarHeader.parseOctal( header, offset, TarHeader.DEVLEN );
690		}
691
692	/**
693	 * Fill in a TarHeader given only the entry's name.
694	 *
695	 * @param hdr The TarHeader to fill in.
696	 * @param name The tar entry name.
697	 */
698	public void
699	nameTarHeader( TarHeader hdr, String name )
700		{
701		boolean isDir = name.endsWith( "/" );
702
703		hdr.checkSum = 0;
704		hdr.devMajor = 0;
705		hdr.devMinor = 0;
706
707		hdr.name = new StringBuffer( name );
708		hdr.mode = isDir ? 040755 : 0100644;
709		hdr.userId = 0;
710		hdr.groupId = 0;
711		hdr.size = 0;
712		hdr.checkSum = 0;
713
714		hdr.modTime =
715			(new java.util.Date()).getTime() / 1000;
716
717		hdr.linkFlag =
718			isDir ? TarHeader.LF_DIR : TarHeader.LF_NORMAL;
719
720		hdr.linkName = new StringBuffer( "" );
721		hdr.userName = new StringBuffer( "" );
722		hdr.groupName = new StringBuffer( "" );
723
724		hdr.devMajor = 0;
725		hdr.devMinor = 0;
726		}
727
728	}
729