View Javadoc

1   package net.sf.jpkgmk.pkgmap;
2   
3   import java.io.File;
4   import java.io.IOException;
5   import java.io.Writer;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.Comparator;
9   import java.util.HashSet;
10  import java.util.Iterator;
11  import java.util.List;
12  import java.util.ListIterator;
13  import java.util.Set;
14  
15  import net.sf.jpkgmk.AbstractFileCreatorAdapter;
16  import net.sf.jpkgmk.DefaultFileHandler;
17  import net.sf.jpkgmk.FileHandler;
18  import net.sf.jpkgmk.PackageException;
19  import net.sf.jpkgmk.util.ObjectUtils;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  
24  /**
25   * pkgmap is an ASCII file that provides a complete listing of the package contents. 
26   * It is automatically generated by pkgmk(1) using the information in the prototype(4) file.
27   * 
28   * Representation of a package map file (called "pkgmap" on the filesystem).
29   * The pkgmap file is a packing list, where each item in the package is defined and its size and 
30   * checksum are stored. The size and checksum help in determining that the package has not 
31   * been tampered with. The various items in the pkgmap are recognized by their ftype entry as shown here.
32   * 
33   * See 
34   * <a href="http://docs.sun.com/app/docs/doc/805-6338">sun docs</a>
35   * <a href="http://www.cs.biu.ac.il/cgi-bin/man?pkgmap+4">http://www.cs.biu.ac.il/cgi-bin/man?pkgmap+4</a> and
36   * <a href="http://www.sun.com/bigadmin/features/techtips/package_components.jsp">http://www.sun.com/bigadmin/features/techtips/package_components.jsp</a>
37   * <a href="http://www.softpanorama.org/Solaris/Packages/index.shtml">http://www.softpanorama.org/Solaris/Packages/index.shtml</a>.
38   * 
39   * @author gommma (gommma AT users.sourceforge.net)
40   * @author Last changed by: $Author: gommma $
41   * @version $Revision: 2 $ $Date: 2008-08-20 21:14:19 +0200 (Mi, 20 Aug 2008) $
42   * @since 1.0
43   */
44  public class PkgMap extends AbstractFileCreatorAdapter
45  {
46      /**
47       * Integer constant for the value {@value}
48       */
49      private static final Integer ONE = Integer.valueOf(1);
50  
51      /**
52  	 * The name of the file on the filesystem
53  	 */
54  	public static final String PKGMAP="pkgmap";
55  
56  	
57  	private Log log = LogFactory.getLog(PkgMap.class);
58  	/**
59  	 * Set holding all entries of the package map
60  	 */
61  	private List<PkgMapEntry> pkgMapEntryList = new ArrayList<PkgMapEntry>();
62  	private PkgMapEntryHeader header;
63  	
64  	/**
65  	 */
66  	public PkgMap() {
67  		super();
68  	}
69  
70  	
71  
72  	@Override
73  	public FileHandler getFileHandler(File targetDir) {
74  		DefaultFileHandler fileCreator = new DefaultFileHandler(targetDir, PKGMAP);
75  		return fileCreator;
76  	}
77  
78  
79  	public void writeContent(Writer writer) throws IOException 
80  	{
81  		// 1. create the pkgmap header
82  		if(this.header == null) {
83  			throw new IllegalStateException("Before creating the pkgmap, the header must be set via setHeader().");
84  		}
85  		header.write(writer);
86  		
87  		// 2. sort the entries
88  		sort(this.pkgMapEntryList);
89  		
90  		// 3. create the lines
91  		for(Iterator<PkgMapEntry> iter=this.pkgMapEntryList.iterator(); iter.hasNext(); ) {
92  			PkgMapEntry entry = (PkgMapEntry)iter.next();
93  			entry.write(writer);
94  		}
95  		writer.flush();
96  	}
97  
98  	/**
99  	 * Sorts the given list of pkgmap entries.
100 	 * <p>
101 	 * As defined in the sun docs:
102 	 * &quot;The package objects in pkgmap are listed in alphabetical order by class and by path name to
103 	 * reduce the time it takes to install the package.&quot;
104 	 * </p>
105 	 * @param pkgMapEntryList2
106 	 */
107 	void sort(List<PkgMapEntry> pkgMapEntryList2) {
108 
109 		log.debug("<entering> sort(List<PkgMapEntry>)");
110 		
111 		List<EntryCommentAssociation> entryCommandAssocList = createEntryCommandAssoc(pkgMapEntryList2);
112 
113 		// Remove comments
114 		List<PkgMapEntry> entryListWithoutComments = filterComments(pkgMapEntryList2);
115 		// Sort the entries now
116 		Collections.sort(entryListWithoutComments, new PkgMapEntrySorter());
117 		// At the end, add the comments at the locations where they belong
118 		List<PkgMapEntry> fullResultList = addCommentsIfNeeded(entryListWithoutComments, entryCommandAssocList);
119 		// assign the result list to this object as new package map
120 		this.pkgMapEntryList = fullResultList;
121 	}
122 
123 	private List<PkgMapEntry> addCommentsIfNeeded(List<PkgMapEntry> entryListWithoutComments,
124 			List<EntryCommentAssociation> entryCommandAssocList) {
125 		
126 		List<PkgMapEntry> resultList = new ArrayList<PkgMapEntry>();
127 		for(PkgMapEntry currentEntry : entryListWithoutComments) {
128 			// Search the entry in the entryCommentAssoc list
129 			for(EntryCommentAssociation entryCommentAssoc : entryCommandAssocList)
130 			{
131 				if(entryCommentAssoc.getEntry().equals(currentEntry)) {
132 					// Add the comments to the entry list
133 					resultList.addAll(entryCommentAssoc.getCommentList());
134 					break;
135 				}
136 			}
137 			// Finally add the entry itself
138 			resultList.add(currentEntry);
139 		}
140 		return resultList;
141 	}
142 
143 
144 	private List<PkgMapEntry> filterComments(List<PkgMapEntry> pkgMapEntryList2) {
145 		List<PkgMapEntry> resultList = new ArrayList<PkgMapEntry>();
146 		for (PkgMapEntry pkgMapEntry : pkgMapEntryList2) {
147 			if( !(pkgMapEntry instanceof PkgMapEntryComment)) {
148 				resultList.add(pkgMapEntry);
149 			}
150 		}
151 		return resultList;
152 	}
153 
154 
155 	private List<EntryCommentAssociation> createEntryCommandAssoc(List<PkgMapEntry> pkgMapEntryList2) {
156 		List<EntryCommentAssociation> assocList = new ArrayList<EntryCommentAssociation>();
157 		List<PkgMapEntryComment> commentList = new ArrayList<PkgMapEntryComment>();
158 
159 		for(PkgMapEntry entry : pkgMapEntryList2) {
160 			if(entry instanceof PkgMapEntryComment) {
161 				// Collect the comment
162 				commentList.add((PkgMapEntryComment)entry);
163 			}
164 			else {
165 				AbstractPkgMapEntry mapEntry = (AbstractPkgMapEntry)entry;
166 				if(!commentList.isEmpty()) {
167 					// Create association of previous comments with this entry
168 					EntryCommentAssociation entryCommentAssoc = new EntryCommentAssociation(mapEntry, commentList);
169 					assocList.add(entryCommentAssoc);
170 					// create new list for comment entries - the old one has finished with the curent entry
171 					commentList = new ArrayList<PkgMapEntryComment>();
172 				}
173 				else {
174 					log.debug("Comment list is empty. No need to create a comment association for current entry.");
175 				}
176 			}
177 		}
178 		return assocList;
179 	}
180 
181 
182 
183 	/**
184 	 * @return Unmodifiable list of the pkgmap entries
185 	 */
186 	public List<PkgMapEntry> getPkgMapEntries()
187 	{
188 		return Collections.unmodifiableList(this.pkgMapEntryList);
189 	}
190 	
191 	/**
192 	 * Adds all the given entries to this pkgmap object.
193 	 * @param pkgMapEntryList
194 	 */
195 	public void addAll(List<PkgMapEntry> pkgMapEntryList) {
196 		for (Iterator<PkgMapEntry> iterator = pkgMapEntryList.iterator(); iterator.hasNext();) {
197 			PkgMapEntry pkgMapEntry = iterator.next();
198 			this.add(pkgMapEntry);
199 		}
200 	}
201 
202 	/**
203 	 * Adds a prototype entry. Does <b>not</b> create the physical file yet
204 	 * @param entry
205 	 * @return Returns <code>true</code> if the entry was added successfully.
206 	 */
207 	public void add(PkgMapEntry entry) {
208 		log.debug("Adding entry: " + entry);
209 		
210 		if(PkgMapEntryHeader.class.isAssignableFrom(entry.getClass())) {
211 			this.setHeader((PkgMapEntryHeader)entry);
212 		}
213 		else {
214 			if(pkgMapEntryList.contains(entry)) {
215 				// Get the entry that already exists in the map
216 				PkgMapEntry existingEntry = findEntryThatEquals(entry);
217 				throw new PackageException("Entry that equals the given one '" + entry + "' already exists in the current map. Equal entry in list: '" + existingEntry + "'.");
218 			}
219 			boolean success = pkgMapEntryList.add(entry);
220 			if(!success) {
221 				throw new PackageException("Failed to add the entry '" + entry + "' to the map.");
222 	//			log.warn("Failed to add the entry '" + entry + "' to the collection '" + pkgMapEntryList + "'.");
223 			}
224 		}
225 	}
226 
227 	/**
228 	 * Sets the only header for this pkgmap. If it was already set before, an exception is thrown.
229 	 * @param header
230 	 */
231 	public void setHeader(PkgMapEntryHeader header) {
232 		if(this.header != null) {
233 			throw new IllegalStateException("The header is already set. Cannot overwrite");
234 		}
235 		if(header == null) {
236 			log.info("Resetting pkgmap header to null");
237 		}
238 		this.header = header;
239 	}
240 
241 	public PkgMapEntryHeader getHeader() {
242 		return this.header;
243 	}
244 	
245 	/**
246 	 * @return Returns the number of entries that are currently in this map
247 	 */
248 	public int getSize() {
249 		return this.pkgMapEntryList.size();
250 	}
251 
252 	/**
253 	 * Searches for an entry in the {@link #pkgMapEntryList} which equals the given entry (current.equals(entry)==true).
254 	 * @param entry The entry to be searched
255 	 * @return The result entry or null if no equal entry was found
256 	 */
257 	private PkgMapEntry findEntryThatEquals(PkgMapEntry entry) 
258 	{
259 		for(Iterator<PkgMapEntry> iter=this.pkgMapEntryList.iterator(); iter.hasNext(); ) {
260 			PkgMapEntry currentEntry = (PkgMapEntry)iter.next();
261 			if(currentEntry.equals(entry)) {
262 				return currentEntry;
263 			}
264 		}
265 		return null;
266 	}
267 
268 	/**
269 	 * @return The number of different parts that exist in the current pkgmap. If the part of an entry
270 	 * is <code>null</code>, the value 1 is assumed by default as defined in the sun specs.
271 	 */
272 	public Integer getNumberOfParts() {
273 		Set<Integer> foundParts = new HashSet<Integer>();
274 		for(Iterator<PkgMapEntry> iter=this.pkgMapEntryList.iterator(); iter.hasNext(); ) {
275 			PkgMapEntry currentEntry = (PkgMapEntry)iter.next();
276 			if(PkgMapEntryFile.class.isAssignableFrom(currentEntry.getClass())) {
277 				PkgMapEntryFile currentEntryFile = (PkgMapEntryFile)currentEntry;
278 				Integer part = currentEntryFile.getPart();
279 				if(part==null) {
280 					// By default, if no part is specified, the pkgadd assumes that "1" is meant.
281 					part = ONE;
282 				}
283 				foundParts.add(part);
284 			}
285 		}
286 		return foundParts.size();
287 	}
288 
289 	@Override
290 	public int hashCode() {
291 		final int prime = 31;
292 		int result = 1;
293 		result = prime * result + ((header == null) ? 0 : header.hashCode());
294 		result = prime * result
295 				+ ((pkgMapEntryList == null) ? 0 : pkgMapEntryList.hashCode());
296 		return result;
297 	}
298 
299 
300 
301 	@Override
302 	public boolean equals(Object obj) {
303 		return this.equals(obj, false);
304 	}
305 
306 	/**
307 	 * @param obj
308 	 * @param ignoreLastModified
309 	 * @return
310 	 */
311 	public boolean equals(Object obj, boolean ignoreLastModified) {
312 		if (this == obj)
313 			return true;
314 		if (obj == null)
315 			return false;
316 		if (getClass() != obj.getClass())
317 			return false;
318 		final PkgMap other = (PkgMap) obj;
319 		
320 		if(!ObjectUtils.areObjectsEqual(this.header, other.header))
321 			return false;
322 
323 		if (pkgMapEntryList == null) {
324 			if (other.pkgMapEntryList != null)
325 				return false;
326 		} else if (!listsEqual(pkgMapEntryList, other.pkgMapEntryList, ignoreLastModified))
327 			return false;
328 		return true;
329 	}
330 
331 	
332 
333 	private boolean listsEqual(List<PkgMapEntry> list1,
334 			List<PkgMapEntry> list2, boolean ignoreLastModified) {
335 		if(!ignoreLastModified) {
336 			return list1.equals(list2);
337 		}
338 		else {
339 			
340 			ListIterator<PkgMapEntry> e1 = list1.listIterator();
341 			ListIterator<PkgMapEntry> e2 = list2.listIterator();
342 			while(e1.hasNext() && e2.hasNext()) {
343 				PkgMapEntry o1 = e1.next();
344 				PkgMapEntry o2 = e2.next();
345 			    if (!(o1==null ? o2==null : o1.equals(o2, ignoreLastModified)))
346 			    	return false;
347 			}
348 			return !(e1.hasNext() || e2.hasNext());
349 		}
350 	}
351 
352 
353 
354 	@Override
355 	public String toString()
356 	{
357 		StringBuffer sb = new StringBuffer();
358 		sb.append(getClass().getName()).append("[");
359 		sb.append(super.toString());
360 		sb.append("[");
361 		sb.append("entries=").append(this.pkgMapEntryList);
362 		sb.append("]");
363 		sb.append("]");
364 		return sb.toString();
365 	}
366 
367 	
368 	
369 	
370 	/**
371 	 * Sorter for PkgMap entries
372 	 */
373 	private static class PkgMapEntrySorter implements Comparator<PkgMapEntry>
374 	{
375 
376 		public int compare(PkgMapEntry o1, PkgMapEntry o2) {
377 			if(o1 instanceof PkgMapEntryHeader) {
378 				return -1;
379 			}
380 			else if(o2 instanceof PkgMapEntryHeader) {
381 				return 1;
382 			}
383 			else if(o1 instanceof AbstractPkgMapEntry && o2 instanceof AbstractPkgMapEntry) {
384 				AbstractPkgMapEntry o1Abstract = (AbstractPkgMapEntry)o1;
385 				AbstractPkgMapEntry o2Abstract = (AbstractPkgMapEntry)o2;
386 				return o1Abstract.compareTo(o2Abstract);
387 			}
388 			else {
389 				throw new UnsupportedOperationException("Unknown pkgmap entry type: " + o1 + " and " + o2);
390 			}
391 		}
392 		
393 	}
394 	
395 	/**
396 	 * Links a pkgmap entry with one or more comments
397 	 */
398 	private static class EntryCommentAssociation
399 	{
400 		private PkgMapEntry entry;
401 		private List<PkgMapEntryComment> commentList;
402 		
403 		public EntryCommentAssociation(PkgMapEntry entry, List<PkgMapEntryComment> commentList)
404 		{
405 			this.entry = entry;
406 			this.commentList = commentList;
407 		}
408 
409 		public PkgMapEntry getEntry() {
410 			return entry;
411 		}
412 
413 		public List<PkgMapEntryComment> getCommentList() {
414 			return commentList;
415 		}
416 		
417 	}
418 
419 }